text: // Copyright (c) 2010 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "base/metrics/field_trial.h"
  
  #include "base/logging.h"
  #include "base/rand_util.h"
  #include "base/stringprintf.h"
  
  namespace base {
  
  // static
  const int FieldTrial::kNotParticipating = -1;
  
  // static
  const int FieldTrial::kAllRemainingProbability = -2;
  
  // static
  bool FieldTrial::enable_benchmarking_ = false;
  
  // static
  const char FieldTrialList::kPersistentStringSeparator('/');
  
  static const char kHistogramFieldTrialSeparator('_');
  
  //------------------------------------------------------------------------------
  // FieldTrial methods and members.
  
  FieldTrial::FieldTrial(const std::string& name,
                         const Probability total_probability)
    : name_(name),
      divisor_(total_probability),
      random_(static_cast<Probability>(divisor_ * base::RandDouble())),
      accumulated_group_probability_(0),
      next_group_number_(0),
      group_(kNotParticipating) {
    FieldTrialList::Register(this);
  }
  
  int FieldTrial::AppendGroup(const std::string& name,
                              Probability group_probability) {
    DCHECK(group_probability <= divisor_);
    DCHECK(group_probability >=0 ||
           group_probability == kAllRemainingProbability);
    if (group_probability == kAllRemainingProbability) {
      accumulated_group_probability_ = divisor_;
    } else {
      if (enable_benchmarking_)
        group_probability = 0;
      accumulated_group_probability_ += group_probability;
    }
    DCHECK(accumulated_group_probability_ <= divisor_);
    if (group_ == kNotParticipating && accumulated_group_probability_ > random_) {
      // This is the group that crossed the random line, so we do the assignment.
      group_ = next_group_number_;
      if (name.empty())
        base::StringAppendF(&group_name_, "%d", group_);
      else
        group_name_ = name;
    }
    return next_group_number_++;
  }
  
  // static
  std::string FieldTrial::MakeName(const std::string& name_prefix,
                                   const std::string& trial_name) {
    std::string big_string(name_prefix);
    big_string.append(1, kHistogramFieldTrialSeparator);
    return big_string.append(FieldTrialList::FindFullName(trial_name));
  }
  
  // static
  void FieldTrial::EnableBenchmarking() {
    DCHECK_EQ(0u, FieldTrialList::GetFieldTrialCount());
    enable_benchmarking_ = true;
  }
  
  FieldTrial::~FieldTrial() {}
  
  //------------------------------------------------------------------------------
  // FieldTrialList methods and members.
  
  // static
  FieldTrialList* FieldTrialList::global_ = NULL;
  
  // static
  bool FieldTrialList::register_without_global_ = false;
  
  FieldTrialList::FieldTrialList() : application_start_time_(TimeTicks::Now()) {
    DCHECK(!global_);
    DCHECK(!register_without_global_);
    global_ = this;
  }
  
  FieldTrialList::~FieldTrialList() {
    AutoLock auto_lock(lock_);
    while (!registered_.empty()) {
      RegistrationList::iterator it = registered_.begin();
      it->second->Release();
      registered_.erase(it->first);
    }
    DCHECK(this == global_);
    global_ = NULL;
  }
  
  // static
  void FieldTrialList::Register(FieldTrial* trial) {
    if (!global_) {
      register_without_global_ = true;
      return;
    }
    AutoLock auto_lock(global_->lock_);
    DCHECK(!global_->PreLockedFind(trial->name()));
    trial->AddRef();
    global_->registered_[trial->name()] = trial;
  }
  
  // static
  int FieldTrialList::FindValue(const std::string& name) {
    FieldTrial* field_trial = Find(name);
    if (field_trial)
      return field_trial->group();
    return FieldTrial::kNotParticipating;
  }
  
  // static
  std::string FieldTrialList::FindFullName(const std::string& name) {
    FieldTrial* field_trial = Find(name);
    if (field_trial)
      return field_trial->group_name();
    return "";
  }
  
  // static
  FieldTrial* FieldTrialList::Find(const std::string& name) {
    if (!global_)
      return NULL;
    AutoLock auto_lock(global_->lock_);
    return global_->PreLockedFind(name);
  }
  
  FieldTrial* FieldTrialList::PreLockedFind(const std::string& name) {
    RegistrationList::iterator it = registered_.find(name);
    if (registered_.end() == it)
      return NULL;
    return it->second;
  }
  
  // static
  void FieldTrialList::StatesToString(std::string* output) {
    if (!global_)
      return;
    DCHECK(output->empty());
    AutoLock auto_lock(global_->lock_);
    for (RegistrationList::iterator it = global_->registered_.begin();
         it != global_->registered_.end(); ++it) {
      const std::string name = it->first;
      const std::string group_name = it->second->group_name();
      if (group_name.empty())
        continue;  // No definitive winner in this trial.
      DCHECK_EQ(name.find(kPersistentStringSeparator), std::string::npos);
      DCHECK_EQ(group_name.find(kPersistentStringSeparator), std::string::npos);
      output->append(name);
      output->append(1, kPersistentStringSeparator);
      output->append(group_name);
      output->append(1, kPersistentStringSeparator);
    }
  }
  
  // static
  bool FieldTrialList::StringAugmentsState(const std::string& prior_state) {
    DCHECK(global_);
    if (prior_state.empty() || !global_)
      return true;
  
    size_t next_item = 0;
    while (next_item < prior_state.length()) {
      size_t name_end = prior_state.find(kPersistentStringSeparator, next_item);
      if (name_end == prior_state.npos || next_item == name_end)
        return false;
      size_t group_name_end = prior_state.find(kPersistentStringSeparator,
                                               name_end + 1);
      if (group_name_end == prior_state.npos || name_end + 1 == group_name_end)
        return false;
      std::string name(prior_state, next_item, name_end - next_item);
      std::string group_name(prior_state, name_end + 1,
                             group_name_end - name_end - 1);
      next_item = group_name_end + 1;
  
      FieldTrial *field_trial(FieldTrialList::Find(name));
      if (field_trial) {
        // In single process mode, we may have already created the field trial.
        if (field_trial->group_name() != group_name)
          return false;
        continue;
      }
      const int kTotalProbability = 100;
      field_trial = new FieldTrial(name, kTotalProbability);
      field_trial->AppendGroup(group_name, kTotalProbability);
    }
    return true;
  }
  
  // static
  size_t FieldTrialList::GetFieldTrialCount() {
    if (!global_)
      return 0;
    AutoLock auto_lock(global_->lock_);
    return global_->registered_.size();
  }
  
  }  // namespace base
  