Home | History | Annotate | Download | only in metrics
      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 "base/metrics/field_trial.h"
      6 
      7 #include "base/build_time.h"
      8 #include "base/logging.h"
      9 #include "base/rand_util.h"
     10 #include "base/sha1.h"
     11 #include "base/strings/string_util.h"
     12 #include "base/strings/stringprintf.h"
     13 #include "base/strings/utf_string_conversions.h"
     14 #include "base/sys_byteorder.h"
     15 
     16 namespace base {
     17 
     18 namespace {
     19 
     20 // Created a time value based on |year|, |month| and |day_of_month| parameters.
     21 Time CreateTimeFromParams(int year, int month, int day_of_month) {
     22   DCHECK_GT(year, 1970);
     23   DCHECK_GT(month, 0);
     24   DCHECK_LT(month, 13);
     25   DCHECK_GT(day_of_month, 0);
     26   DCHECK_LT(day_of_month, 32);
     27 
     28   Time::Exploded exploded;
     29   exploded.year = year;
     30   exploded.month = month;
     31   exploded.day_of_week = 0;  // Should be unused.
     32   exploded.day_of_month = day_of_month;
     33   exploded.hour = 0;
     34   exploded.minute = 0;
     35   exploded.second = 0;
     36   exploded.millisecond = 0;
     37 
     38   return Time::FromLocalExploded(exploded);
     39 }
     40 
     41 }  // namespace
     42 
     43 static const char kHistogramFieldTrialSeparator('_');
     44 
     45 // statics
     46 const int FieldTrial::kNotFinalized = -1;
     47 const int FieldTrial::kDefaultGroupNumber = 0;
     48 bool FieldTrial::enable_benchmarking_ = false;
     49 
     50 const char FieldTrialList::kPersistentStringSeparator('/');
     51 int FieldTrialList::kNoExpirationYear = 0;
     52 
     53 //------------------------------------------------------------------------------
     54 // FieldTrial methods and members.
     55 
     56 FieldTrial::FieldTrial(const std::string& trial_name,
     57                        const Probability total_probability,
     58                        const std::string& default_group_name,
     59                        double entropy_value)
     60     : trial_name_(trial_name),
     61       divisor_(total_probability),
     62       default_group_name_(default_group_name),
     63       random_(static_cast<Probability>(divisor_ * entropy_value)),
     64       accumulated_group_probability_(0),
     65       next_group_number_(kDefaultGroupNumber + 1),
     66       group_(kNotFinalized),
     67       enable_field_trial_(true),
     68       forced_(false),
     69       group_reported_(false) {
     70   DCHECK_GT(total_probability, 0);
     71   DCHECK(!trial_name_.empty());
     72   DCHECK(!default_group_name_.empty());
     73 }
     74 
     75 FieldTrial::EntropyProvider::~EntropyProvider() {
     76 }
     77 
     78 void FieldTrial::Disable() {
     79   DCHECK(!group_reported_);
     80   enable_field_trial_ = false;
     81 
     82   // In case we are disabled after initialization, we need to switch
     83   // the trial to the default group.
     84   if (group_ != kNotFinalized) {
     85     // Only reset when not already the default group, because in case we were
     86     // forced to the default group, the group number may not be
     87     // kDefaultGroupNumber, so we should keep it as is.
     88     if (group_name_ != default_group_name_)
     89       SetGroupChoice(default_group_name_, kDefaultGroupNumber);
     90   }
     91 }
     92 
     93 int FieldTrial::AppendGroup(const std::string& name,
     94                             Probability group_probability) {
     95   // When the group choice was previously forced, we only need to return the
     96   // the id of the chosen group, and anything can be returned for the others.
     97   if (forced_) {
     98     DCHECK(!group_name_.empty());
     99     if (name == group_name_) {
    100       // Note that while |group_| may be equal to |kDefaultGroupNumber| on the
    101       // forced trial, it will not have the same value as the default group
    102       // number returned from the non-forced |FactoryGetFieldTrial()| call,
    103       // which takes care to ensure that this does not happen.
    104       return group_;
    105     }
    106     DCHECK_NE(next_group_number_, group_);
    107     // We still return different numbers each time, in case some caller need
    108     // them to be different.
    109     return next_group_number_++;
    110   }
    111 
    112   DCHECK_LE(group_probability, divisor_);
    113   DCHECK_GE(group_probability, 0);
    114 
    115   if (enable_benchmarking_ || !enable_field_trial_)
    116     group_probability = 0;
    117 
    118   accumulated_group_probability_ += group_probability;
    119 
    120   DCHECK_LE(accumulated_group_probability_, divisor_);
    121   if (group_ == kNotFinalized && accumulated_group_probability_ > random_) {
    122     // This is the group that crossed the random line, so we do the assignment.
    123     SetGroupChoice(name, next_group_number_);
    124   }
    125   return next_group_number_++;
    126 }
    127 
    128 int FieldTrial::group() {
    129   FinalizeGroupChoice();
    130   FieldTrialList::NotifyFieldTrialGroupSelection(this);
    131   return group_;
    132 }
    133 
    134 const std::string& FieldTrial::group_name() {
    135   // Call |group()| to ensure group gets assigned and observers are notified.
    136   group();
    137   DCHECK(!group_name_.empty());
    138   return group_name_;
    139 }
    140 
    141 // static
    142 std::string FieldTrial::MakeName(const std::string& name_prefix,
    143                                  const std::string& trial_name) {
    144   std::string big_string(name_prefix);
    145   big_string.append(1, kHistogramFieldTrialSeparator);
    146   return big_string.append(FieldTrialList::FindFullName(trial_name));
    147 }
    148 
    149 // static
    150 void FieldTrial::EnableBenchmarking() {
    151   DCHECK_EQ(0u, FieldTrialList::GetFieldTrialCount());
    152   enable_benchmarking_ = true;
    153 }
    154 
    155 void FieldTrial::SetForced() {
    156   // We might have been forced before (e.g., by CreateFieldTrial) and it's
    157   // first come first served, e.g., command line switch has precedence.
    158   if (forced_)
    159     return;
    160 
    161   // And we must finalize the group choice before we mark ourselves as forced.
    162   FinalizeGroupChoice();
    163   forced_ = true;
    164 }
    165 
    166 FieldTrial::~FieldTrial() {}
    167 
    168 void FieldTrial::SetGroupChoice(const std::string& group_name, int number) {
    169   group_ = number;
    170   if (group_name.empty())
    171     StringAppendF(&group_name_, "%d", group_);
    172   else
    173     group_name_ = group_name;
    174   DVLOG(1) << "Field trial: " << trial_name_ << " Group choice:" << group_name_;
    175 }
    176 
    177 void FieldTrial::FinalizeGroupChoice() {
    178   if (group_ != kNotFinalized)
    179     return;
    180   accumulated_group_probability_ = divisor_;
    181   // Here it's OK to use |kDefaultGroupNumber| since we can't be forced and not
    182   // finalized.
    183   DCHECK(!forced_);
    184   SetGroupChoice(default_group_name_, kDefaultGroupNumber);
    185 }
    186 
    187 bool FieldTrial::GetActiveGroup(ActiveGroup* active_group) const {
    188   if (!group_reported_ || !enable_field_trial_)
    189     return false;
    190   DCHECK_NE(group_, kNotFinalized);
    191   active_group->trial_name = trial_name_;
    192   active_group->group_name = group_name_;
    193   return true;
    194 }
    195 
    196 //------------------------------------------------------------------------------
    197 // FieldTrialList methods and members.
    198 
    199 // static
    200 FieldTrialList* FieldTrialList::global_ = NULL;
    201 
    202 // static
    203 bool FieldTrialList::used_without_global_ = false;
    204 
    205 FieldTrialList::Observer::~Observer() {
    206 }
    207 
    208 FieldTrialList::FieldTrialList(
    209     const FieldTrial::EntropyProvider* entropy_provider)
    210     : entropy_provider_(entropy_provider),
    211       observer_list_(new ObserverListThreadSafe<FieldTrialList::Observer>(
    212           ObserverListBase<FieldTrialList::Observer>::NOTIFY_EXISTING_ONLY)) {
    213   DCHECK(!global_);
    214   DCHECK(!used_without_global_);
    215   global_ = this;
    216 
    217   Time two_years_from_build_time = GetBuildTime() + TimeDelta::FromDays(730);
    218   Time::Exploded exploded;
    219   two_years_from_build_time.LocalExplode(&exploded);
    220   kNoExpirationYear = exploded.year;
    221 }
    222 
    223 FieldTrialList::~FieldTrialList() {
    224   AutoLock auto_lock(lock_);
    225   while (!registered_.empty()) {
    226     RegistrationList::iterator it = registered_.begin();
    227     it->second->Release();
    228     registered_.erase(it->first);
    229   }
    230   DCHECK_EQ(this, global_);
    231   global_ = NULL;
    232 }
    233 
    234 // static
    235 FieldTrial* FieldTrialList::FactoryGetFieldTrial(
    236     const std::string& trial_name,
    237     FieldTrial::Probability total_probability,
    238     const std::string& default_group_name,
    239     const int year,
    240     const int month,
    241     const int day_of_month,
    242     FieldTrial::RandomizationType randomization_type,
    243     int* default_group_number) {
    244   return FactoryGetFieldTrialWithRandomizationSeed(
    245       trial_name, total_probability, default_group_name,
    246       year, month, day_of_month, randomization_type, 0, default_group_number);
    247 }
    248 
    249 // static
    250 FieldTrial* FieldTrialList::FactoryGetFieldTrialWithRandomizationSeed(
    251     const std::string& trial_name,
    252     FieldTrial::Probability total_probability,
    253     const std::string& default_group_name,
    254     const int year,
    255     const int month,
    256     const int day_of_month,
    257     FieldTrial::RandomizationType randomization_type,
    258     uint32 randomization_seed,
    259     int* default_group_number) {
    260   if (default_group_number)
    261     *default_group_number = FieldTrial::kDefaultGroupNumber;
    262   // Check if the field trial has already been created in some other way.
    263   FieldTrial* existing_trial = Find(trial_name);
    264   if (existing_trial) {
    265     CHECK(existing_trial->forced_);
    266     // If the default group name differs between the existing forced trial
    267     // and this trial, then use a different value for the default group number.
    268     if (default_group_number &&
    269         default_group_name != existing_trial->default_group_name()) {
    270       // If the new default group number corresponds to the group that was
    271       // chosen for the forced trial (which has been finalized when it was
    272       // forced), then set the default group number to that.
    273       if (default_group_name == existing_trial->group_name_internal()) {
    274         *default_group_number = existing_trial->group_;
    275       } else {
    276         // Otherwise, use |kNonConflictingGroupNumber| (-2) for the default
    277         // group number, so that it does not conflict with the |AppendGroup()|
    278         // result for the chosen group.
    279         const int kNonConflictingGroupNumber = -2;
    280         COMPILE_ASSERT(
    281             kNonConflictingGroupNumber != FieldTrial::kDefaultGroupNumber,
    282             conflicting_default_group_number);
    283         COMPILE_ASSERT(
    284             kNonConflictingGroupNumber != FieldTrial::kNotFinalized,
    285             conflicting_default_group_number);
    286         *default_group_number = kNonConflictingGroupNumber;
    287       }
    288     }
    289     return existing_trial;
    290   }
    291 
    292   double entropy_value;
    293   if (randomization_type == FieldTrial::ONE_TIME_RANDOMIZED) {
    294     entropy_value = GetEntropyProviderForOneTimeRandomization()->
    295           GetEntropyForTrial(trial_name, randomization_seed);
    296   } else {
    297     DCHECK_EQ(FieldTrial::SESSION_RANDOMIZED, randomization_type);
    298     DCHECK_EQ(0U, randomization_seed);
    299     entropy_value = RandDouble();
    300   }
    301 
    302   FieldTrial* field_trial = new FieldTrial(trial_name, total_probability,
    303                                            default_group_name, entropy_value);
    304   if (GetBuildTime() > CreateTimeFromParams(year, month, day_of_month))
    305     field_trial->Disable();
    306   FieldTrialList::Register(field_trial);
    307   return field_trial;
    308 }
    309 
    310 // static
    311 FieldTrial* FieldTrialList::Find(const std::string& name) {
    312   if (!global_)
    313     return NULL;
    314   AutoLock auto_lock(global_->lock_);
    315   return global_->PreLockedFind(name);
    316 }
    317 
    318 // static
    319 int FieldTrialList::FindValue(const std::string& name) {
    320   FieldTrial* field_trial = Find(name);
    321   if (field_trial)
    322     return field_trial->group();
    323   return FieldTrial::kNotFinalized;
    324 }
    325 
    326 // static
    327 std::string FieldTrialList::FindFullName(const std::string& name) {
    328   FieldTrial* field_trial = Find(name);
    329   if (field_trial)
    330     return field_trial->group_name();
    331   return std::string();
    332 }
    333 
    334 // static
    335 bool FieldTrialList::TrialExists(const std::string& name) {
    336   return Find(name) != NULL;
    337 }
    338 
    339 // static
    340 void FieldTrialList::StatesToString(std::string* output) {
    341   FieldTrial::ActiveGroups active_groups;
    342   GetActiveFieldTrialGroups(&active_groups);
    343   for (FieldTrial::ActiveGroups::const_iterator it = active_groups.begin();
    344        it != active_groups.end(); ++it) {
    345     DCHECK_EQ(std::string::npos,
    346               it->trial_name.find(kPersistentStringSeparator));
    347     DCHECK_EQ(std::string::npos,
    348               it->group_name.find(kPersistentStringSeparator));
    349     output->append(it->trial_name);
    350     output->append(1, kPersistentStringSeparator);
    351     output->append(it->group_name);
    352     output->append(1, kPersistentStringSeparator);
    353   }
    354 }
    355 
    356 // static
    357 void FieldTrialList::GetActiveFieldTrialGroups(
    358     FieldTrial::ActiveGroups* active_groups) {
    359   DCHECK(active_groups->empty());
    360   if (!global_)
    361     return;
    362   AutoLock auto_lock(global_->lock_);
    363 
    364   for (RegistrationList::iterator it = global_->registered_.begin();
    365        it != global_->registered_.end(); ++it) {
    366     FieldTrial::ActiveGroup active_group;
    367     if (it->second->GetActiveGroup(&active_group))
    368       active_groups->push_back(active_group);
    369   }
    370 }
    371 
    372 // static
    373 bool FieldTrialList::CreateTrialsFromString(const std::string& trials_string,
    374                                             FieldTrialActivationMode mode) {
    375   DCHECK(global_);
    376   if (trials_string.empty() || !global_)
    377     return true;
    378 
    379   size_t next_item = 0;
    380   while (next_item < trials_string.length()) {
    381     size_t name_end = trials_string.find(kPersistentStringSeparator, next_item);
    382     if (name_end == trials_string.npos || next_item == name_end)
    383       return false;
    384     size_t group_name_end = trials_string.find(kPersistentStringSeparator,
    385                                                name_end + 1);
    386     if (group_name_end == trials_string.npos || name_end + 1 == group_name_end)
    387       return false;
    388     std::string name(trials_string, next_item, name_end - next_item);
    389     std::string group_name(trials_string, name_end + 1,
    390                            group_name_end - name_end - 1);
    391     next_item = group_name_end + 1;
    392 
    393     FieldTrial* trial = CreateFieldTrial(name, group_name);
    394     if (!trial)
    395       return false;
    396     if (mode == ACTIVATE_TRIALS) {
    397       // Call |group()| to mark the trial as "used" and notify observers, if
    398       // any. This is useful to ensure that field trials created in child
    399       // processes are properly reported in crash reports.
    400       trial->group();
    401     }
    402   }
    403   return true;
    404 }
    405 
    406 // static
    407 FieldTrial* FieldTrialList::CreateFieldTrial(
    408     const std::string& name,
    409     const std::string& group_name) {
    410   DCHECK(global_);
    411   DCHECK_GE(name.size(), 0u);
    412   DCHECK_GE(group_name.size(), 0u);
    413   if (name.empty() || group_name.empty() || !global_)
    414     return NULL;
    415 
    416   FieldTrial* field_trial = FieldTrialList::Find(name);
    417   if (field_trial) {
    418     // In single process mode, or when we force them from the command line,
    419     // we may have already created the field trial.
    420     if (field_trial->group_name_internal() != group_name)
    421       return NULL;
    422     return field_trial;
    423   }
    424   const int kTotalProbability = 100;
    425   field_trial = new FieldTrial(name, kTotalProbability, group_name, 0);
    426   // Force the trial, which will also finalize the group choice.
    427   field_trial->SetForced();
    428   FieldTrialList::Register(field_trial);
    429   return field_trial;
    430 }
    431 
    432 // static
    433 void FieldTrialList::AddObserver(Observer* observer) {
    434   if (!global_)
    435     return;
    436   global_->observer_list_->AddObserver(observer);
    437 }
    438 
    439 // static
    440 void FieldTrialList::RemoveObserver(Observer* observer) {
    441   if (!global_)
    442     return;
    443   global_->observer_list_->RemoveObserver(observer);
    444 }
    445 
    446 // static
    447 void FieldTrialList::NotifyFieldTrialGroupSelection(FieldTrial* field_trial) {
    448   if (!global_)
    449     return;
    450 
    451   {
    452     AutoLock auto_lock(global_->lock_);
    453     if (field_trial->group_reported_)
    454       return;
    455     field_trial->group_reported_ = true;
    456   }
    457 
    458   if (!field_trial->enable_field_trial_)
    459     return;
    460 
    461   global_->observer_list_->Notify(
    462       &FieldTrialList::Observer::OnFieldTrialGroupFinalized,
    463       field_trial->trial_name(),
    464       field_trial->group_name_internal());
    465 }
    466 
    467 // static
    468 size_t FieldTrialList::GetFieldTrialCount() {
    469   if (!global_)
    470     return 0;
    471   AutoLock auto_lock(global_->lock_);
    472   return global_->registered_.size();
    473 }
    474 
    475 // static
    476 const FieldTrial::EntropyProvider*
    477     FieldTrialList::GetEntropyProviderForOneTimeRandomization() {
    478   if (!global_) {
    479     used_without_global_ = true;
    480     return NULL;
    481   }
    482 
    483   return global_->entropy_provider_.get();
    484 }
    485 
    486 FieldTrial* FieldTrialList::PreLockedFind(const std::string& name) {
    487   RegistrationList::iterator it = registered_.find(name);
    488   if (registered_.end() == it)
    489     return NULL;
    490   return it->second;
    491 }
    492 
    493 // static
    494 void FieldTrialList::Register(FieldTrial* trial) {
    495   if (!global_) {
    496     used_without_global_ = true;
    497     return;
    498   }
    499   AutoLock auto_lock(global_->lock_);
    500   DCHECK(!global_->PreLockedFind(trial->trial_name()));
    501   trial->AddRef();
    502   global_->registered_[trial->trial_name()] = trial;
    503 }
    504 
    505 }  // namespace base
    506