diff options
Diffstat (limited to 'base/metrics/field_trial.cc')
-rw-r--r-- | base/metrics/field_trial.cc | 745 |
1 files changed, 18 insertions, 727 deletions
diff --git a/base/metrics/field_trial.cc b/base/metrics/field_trial.cc index 6b38d55bbd..600b94ed48 100644 --- a/base/metrics/field_trial.cc +++ b/base/metrics/field_trial.cc @@ -5,26 +5,15 @@ #include "base/metrics/field_trial.h" #include <algorithm> -#include <utility> -#include "base/base_switches.h" #include "base/build_time.h" -#include "base/command_line.h" -#include "base/debug/activity_tracker.h" #include "base/logging.h" -#include "base/metrics/field_trial_param_associator.h" -#include "base/process/memory.h" #include "base/rand_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" -// On POSIX, the fd is shared using the mapping in GlobalDescriptors. -#if defined(OS_POSIX) && !defined(OS_NACL) -#include "base/posix/global_descriptors.h" -#endif - namespace base { namespace { @@ -38,62 +27,6 @@ const char kPersistentStringSeparator = '/'; // Currently a slash. // command line which forces its activation. const char kActivationMarker = '*'; -// Use shared memory to communicate field trial (experiment) state. Set to false -// for now while the implementation is fleshed out (e.g. data format, single -// shared memory segment). See https://codereview.chromium.org/2365273004/ and -// crbug.com/653874 -// The browser is the only process that has write access to the shared memory. -// This is safe from race conditions because MakeIterable is a release operation -// and GetNextOfType is an acquire operation, so memory writes before -// MakeIterable happen before memory reads after GetNextOfType. -const bool kUseSharedMemoryForFieldTrials = true; - -// Constants for the field trial allocator. -const char kAllocatorName[] = "FieldTrialAllocator"; - -// We allocate 128 KiB to hold all the field trial data. This should be enough, -// as most people use 3 - 25 KiB for field trials (as of 11/25/2016). -// This also doesn't allocate all 128 KiB at once -- the pages only get mapped -// to physical memory when they are touched. If the size of the allocated field -// trials does get larger than 128 KiB, then we will drop some field trials in -// child processes, leading to an inconsistent view between browser and child -// processes and possibly causing crashes (see crbug.com/661617). -const size_t kFieldTrialAllocationSize = 128 << 10; // 128 KiB - -// Writes out string1 and then string2 to pickle. -bool WriteStringPair(Pickle* pickle, - const StringPiece& string1, - const StringPiece& string2) { - if (!pickle->WriteString(string1)) - return false; - if (!pickle->WriteString(string2)) - return false; - return true; -} - -// Writes out the field trial's contents (via trial_state) to the pickle. The -// format of the pickle looks like: -// TrialName, GroupName, ParamKey1, ParamValue1, ParamKey2, ParamValue2, ... -// If there are no parameters, then it just ends at GroupName. -bool PickleFieldTrial(const FieldTrial::State& trial_state, Pickle* pickle) { - if (!WriteStringPair(pickle, *trial_state.trial_name, - *trial_state.group_name)) { - return false; - } - - // Get field trial params. - std::map<std::string, std::string> params; - FieldTrialParamAssociator::GetInstance()->GetFieldTrialParamsWithoutFallback( - *trial_state.trial_name, *trial_state.group_name, ¶ms); - - // Write params to pickle. - for (const auto& param : params) { - if (!WriteStringPair(pickle, param.first, param.second)) - return false; - } - return true; -} - // Created a time value based on |year|, |month| and |day_of_month| parameters. Time CreateTimeFromParams(int year, int month, int day_of_month) { DCHECK_GT(year, 1970); @@ -140,18 +73,11 @@ FieldTrial::Probability GetGroupBoundaryValue( return std::min(result, divisor - 1); } -// Separate type from FieldTrial::State so that it can use StringPieces. -struct FieldTrialStringEntry { - StringPiece trial_name; - StringPiece group_name; - bool activated = false; -}; - // Parses the --force-fieldtrials string |trials_string| into |entries|. // Returns true if the string was parsed correctly. On failure, the |entries| // array may end up being partially filled. bool ParseFieldTrialsString(const std::string& trials_string, - std::vector<FieldTrialStringEntry>* entries) { + std::vector<FieldTrial::State>* entries) { const StringPiece trials_string_piece(trials_string); size_t next_item = 0; @@ -166,7 +92,7 @@ bool ParseFieldTrialsString(const std::string& trials_string, if (group_name_end == trials_string.npos) group_name_end = trials_string.length(); - FieldTrialStringEntry entry; + FieldTrial::State entry; // Verify if the trial should be activated or not. if (trials_string[next_item] == kActivationMarker) { // Name cannot be only the indicator. @@ -181,61 +107,11 @@ bool ParseFieldTrialsString(const std::string& trials_string, trials_string_piece.substr(name_end + 1, group_name_end - name_end - 1); next_item = group_name_end + 1; - entries->push_back(std::move(entry)); + entries->push_back(entry); } return true; } -void AddFeatureAndFieldTrialFlags(const char* enable_features_switch, - const char* disable_features_switch, - CommandLine* cmd_line) { - std::string enabled_features; - std::string disabled_features; - FeatureList::GetInstance()->GetFeatureOverrides(&enabled_features, - &disabled_features); - - if (!enabled_features.empty()) - cmd_line->AppendSwitchASCII(enable_features_switch, enabled_features); - if (!disabled_features.empty()) - cmd_line->AppendSwitchASCII(disable_features_switch, disabled_features); - - std::string field_trial_states; - FieldTrialList::AllStatesToString(&field_trial_states); - if (!field_trial_states.empty()) { - cmd_line->AppendSwitchASCII(switches::kForceFieldTrials, - field_trial_states); - } -} - -#if defined(OS_WIN) -HANDLE CreateReadOnlyHandle(FieldTrialList::FieldTrialAllocator* allocator) { - HANDLE src = allocator->shared_memory()->handle().GetHandle(); - ProcessHandle process = GetCurrentProcess(); - DWORD access = SECTION_MAP_READ | SECTION_QUERY; - HANDLE dst; - if (!::DuplicateHandle(process, src, process, &dst, access, true, 0)) - return kInvalidPlatformFile; - return dst; -} -#endif - -#if defined(OS_POSIX) && !defined(OS_NACL) -int CreateReadOnlyHandle(FieldTrialList::FieldTrialAllocator* allocator) { - SharedMemoryHandle new_handle; - allocator->shared_memory()->ShareReadOnlyToProcess(GetCurrentProcessHandle(), - &new_handle); - return SharedMemory::GetFdFromSharedMemoryHandle(new_handle); -} -#endif - -void OnOutOfMemory(size_t size) { -#if defined(OS_NACL) - NOTREACHED(); -#else - TerminateBecauseOutOfMemory(size); -#endif -} - } // namespace // statics @@ -251,55 +127,12 @@ int FieldTrialList::kNoExpirationYear = 0; FieldTrial::EntropyProvider::~EntropyProvider() { } -FieldTrial::State::State() {} +FieldTrial::State::State() : activated(false) {} FieldTrial::State::State(const State& other) = default; FieldTrial::State::~State() {} -bool FieldTrial::FieldTrialEntry::GetTrialAndGroupName( - StringPiece* trial_name, - StringPiece* group_name) const { - PickleIterator iter = GetPickleIterator(); - return ReadStringPair(&iter, trial_name, group_name); -} - -bool FieldTrial::FieldTrialEntry::GetParams( - std::map<std::string, std::string>* params) const { - PickleIterator iter = GetPickleIterator(); - StringPiece tmp; - // Skip reading trial and group name. - if (!ReadStringPair(&iter, &tmp, &tmp)) - return false; - - while (true) { - StringPiece key; - StringPiece value; - if (!ReadStringPair(&iter, &key, &value)) - return key.empty(); // Non-empty is bad: got one of a pair. - (*params)[key.as_string()] = value.as_string(); - } -} - -PickleIterator FieldTrial::FieldTrialEntry::GetPickleIterator() const { - const char* src = - reinterpret_cast<const char*>(this) + sizeof(FieldTrialEntry); - - Pickle pickle(src, pickle_size); - return PickleIterator(pickle); -} - -bool FieldTrial::FieldTrialEntry::ReadStringPair( - PickleIterator* iter, - StringPiece* trial_name, - StringPiece* group_name) const { - if (!iter->ReadStringPiece(trial_name)) - return false; - if (!iter->ReadStringPiece(group_name)) - return false; - return true; -} - void FieldTrial::Disable() { DCHECK(!group_reported_); enable_field_trial_ = false; @@ -410,8 +243,7 @@ FieldTrial::FieldTrial(const std::string& trial_name, enable_field_trial_(true), forced_(false), group_reported_(false), - trial_registered_(false), - ref_(FieldTrialList::FieldTrialAllocator::kReferenceNull) { + trial_registered_(false) { DCHECK_GT(total_probability, 0); DCHECK(!trial_name_.empty()); DCHECK(!default_group_name_.empty()); @@ -435,10 +267,6 @@ void FieldTrial::SetGroupChoice(const std::string& group_name, int number) { } void FieldTrial::FinalizeGroupChoice() { - FinalizeGroupChoiceImpl(false); -} - -void FieldTrial::FinalizeGroupChoiceImpl(bool is_locked) { if (group_ != kNotFinalized) return; accumulated_group_probability_ = divisor_; @@ -446,10 +274,6 @@ void FieldTrial::FinalizeGroupChoiceImpl(bool is_locked) { // finalized. DCHECK(!forced_); SetGroupChoice(default_group_name_, kDefaultGroupNumber); - - // Add the field trial to shared memory. - if (kUseSharedMemoryForFieldTrials && trial_registered_) - FieldTrialList::OnGroupFinalized(is_locked, this); } bool FieldTrial::GetActiveGroup(ActiveGroup* active_group) const { @@ -465,18 +289,8 @@ bool FieldTrial::GetState(State* field_trial_state) { if (!enable_field_trial_) return false; FinalizeGroupChoice(); - field_trial_state->trial_name = &trial_name_; - field_trial_state->group_name = &group_name_; - field_trial_state->activated = group_reported_; - return true; -} - -bool FieldTrial::GetStateWhileLocked(State* field_trial_state) { - if (!enable_field_trial_) - return false; - FinalizeGroupChoiceImpl(true); - field_trial_state->trial_name = &trial_name_; - field_trial_state->group_name = &group_name_; + field_trial_state->trial_name = trial_name_; + field_trial_state->group_name = group_name_; field_trial_state->activated = group_reported_; return true; } @@ -494,8 +308,8 @@ FieldTrialList::Observer::~Observer() { } FieldTrialList::FieldTrialList( - std::unique_ptr<const FieldTrial::EntropyProvider> entropy_provider) - : entropy_provider_(std::move(entropy_provider)), + const FieldTrial::EntropyProvider* entropy_provider) + : entropy_provider_(entropy_provider), observer_list_(new ObserverListThreadSafe<FieldTrialList::Observer>( ObserverListBase<FieldTrialList::Observer>::NOTIFY_EXISTING_ONLY)) { DCHECK(!global_); @@ -661,17 +475,17 @@ void FieldTrialList::AllStatesToString(std::string* output) { for (const auto& registered : global_->registered_) { FieldTrial::State trial; - if (!registered.second->GetStateWhileLocked(&trial)) + if (!registered.second->GetState(&trial)) continue; DCHECK_EQ(std::string::npos, - trial.trial_name->find(kPersistentStringSeparator)); + trial.trial_name.find(kPersistentStringSeparator)); DCHECK_EQ(std::string::npos, - trial.group_name->find(kPersistentStringSeparator)); + trial.group_name.find(kPersistentStringSeparator)); if (trial.activated) output->append(1, kActivationMarker); - output->append(*trial.trial_name); + trial.trial_name.AppendToString(output); output->append(1, kPersistentStringSeparator); - output->append(*trial.group_name); + trial.group_name.AppendToString(output); output->append(1, kPersistentStringSeparator); } } @@ -696,7 +510,7 @@ void FieldTrialList::GetActiveFieldTrialGroups( void FieldTrialList::GetActiveFieldTrialGroupsFromString( const std::string& trials_string, FieldTrial::ActiveGroups* active_groups) { - std::vector<FieldTrialStringEntry> entries; + std::vector<FieldTrial::State> entries; if (!ParseFieldTrialsString(trials_string, &entries)) return; @@ -711,36 +525,6 @@ void FieldTrialList::GetActiveFieldTrialGroupsFromString( } // static -void FieldTrialList::GetInitiallyActiveFieldTrials( - const base::CommandLine& command_line, - FieldTrial::ActiveGroups* active_groups) { - DCHECK(global_->create_trials_from_command_line_called_); - - if (!global_->field_trial_allocator_) { - GetActiveFieldTrialGroupsFromString( - command_line.GetSwitchValueASCII(switches::kForceFieldTrials), - active_groups); - return; - } - - FieldTrialAllocator* allocator = global_->field_trial_allocator_.get(); - FieldTrialAllocator::Iterator mem_iter(allocator); - const FieldTrial::FieldTrialEntry* entry; - while ((entry = mem_iter.GetNextOfObject<FieldTrial::FieldTrialEntry>()) != - nullptr) { - StringPiece trial_name; - StringPiece group_name; - if (subtle::NoBarrier_Load(&entry->activated) && - entry->GetTrialAndGroupName(&trial_name, &group_name)) { - FieldTrial::ActiveGroup group; - group.trial_name = trial_name.as_string(); - group.group_name = group_name.as_string(); - active_groups->push_back(group); - } - } -} - -// static bool FieldTrialList::CreateTrialsFromString( const std::string& trials_string, const std::set<std::string>& ignored_trial_names) { @@ -748,7 +532,7 @@ bool FieldTrialList::CreateTrialsFromString( if (trials_string.empty() || !global_) return true; - std::vector<FieldTrialStringEntry> entries; + std::vector<FieldTrial::State> entries; if (!ParseFieldTrialsString(trials_string, &entries)) return false; @@ -773,145 +557,6 @@ bool FieldTrialList::CreateTrialsFromString( } // static -void FieldTrialList::CreateTrialsFromCommandLine( - const CommandLine& cmd_line, - const char* field_trial_handle_switch, - int fd_key) { - global_->create_trials_from_command_line_called_ = true; - -#if defined(OS_WIN) - if (cmd_line.HasSwitch(field_trial_handle_switch)) { - std::string handle_switch = - cmd_line.GetSwitchValueASCII(field_trial_handle_switch); - bool result = CreateTrialsFromHandleSwitch(handle_switch); - DCHECK(result); - } -#endif - -#if defined(OS_POSIX) && !defined(OS_NACL) - // On POSIX, we check if the handle is valid by seeing if the browser process - // sent over the switch (we don't care about the value). Invalid handles - // occur in some browser tests which don't initialize the allocator. - if (cmd_line.HasSwitch(field_trial_handle_switch)) { - bool result = CreateTrialsFromDescriptor(fd_key); - DCHECK(result); - } -#endif - - if (cmd_line.HasSwitch(switches::kForceFieldTrials)) { - bool result = FieldTrialList::CreateTrialsFromString( - cmd_line.GetSwitchValueASCII(switches::kForceFieldTrials), - std::set<std::string>()); - DCHECK(result); - } -} - -// static -void FieldTrialList::CreateFeaturesFromCommandLine( - const base::CommandLine& command_line, - const char* enable_features_switch, - const char* disable_features_switch, - FeatureList* feature_list) { - // Fallback to command line if not using shared memory. - if (!kUseSharedMemoryForFieldTrials || - !global_->field_trial_allocator_.get()) { - return feature_list->InitializeFromCommandLine( - command_line.GetSwitchValueASCII(enable_features_switch), - command_line.GetSwitchValueASCII(disable_features_switch)); - } - - feature_list->InitializeFromSharedMemory( - global_->field_trial_allocator_.get()); -} - -#if defined(OS_WIN) -// static -void FieldTrialList::AppendFieldTrialHandleIfNeeded( - HandlesToInheritVector* handles) { - if (!global_) - return; - if (kUseSharedMemoryForFieldTrials) { - InstantiateFieldTrialAllocatorIfNeeded(); - if (global_->readonly_allocator_handle_) - handles->push_back(global_->readonly_allocator_handle_); - } -} -#endif - -#if defined(OS_POSIX) && !defined(OS_NACL) -// static -int FieldTrialList::GetFieldTrialHandle() { - if (global_ && kUseSharedMemoryForFieldTrials) { - InstantiateFieldTrialAllocatorIfNeeded(); - // We check for an invalid handle where this gets called. - return global_->readonly_allocator_handle_; - } - return kInvalidPlatformFile; -} -#endif - -// static -void FieldTrialList::CopyFieldTrialStateToFlags( - const char* field_trial_handle_switch, - const char* enable_features_switch, - const char* disable_features_switch, - CommandLine* cmd_line) { - // TODO(lawrencewu): Ideally, having the global would be guaranteed. However, - // content browser tests currently don't create a FieldTrialList because they - // don't run ChromeBrowserMainParts code where it's done for Chrome. - // Some tests depend on the enable and disable features flag switch, though, - // so we can still add those even though AllStatesToString() will be a no-op. - if (!global_) { - AddFeatureAndFieldTrialFlags(enable_features_switch, - disable_features_switch, cmd_line); - return; - } - - // Use shared memory to pass the state if the feature is enabled, otherwise - // fallback to passing it via the command line as a string. - if (kUseSharedMemoryForFieldTrials) { - InstantiateFieldTrialAllocatorIfNeeded(); - // If the readonly handle didn't get duplicated properly, then fallback to - // original behavior. - if (global_->readonly_allocator_handle_ == kInvalidPlatformFile) { - AddFeatureAndFieldTrialFlags(enable_features_switch, - disable_features_switch, cmd_line); - return; - } - - global_->field_trial_allocator_->UpdateTrackingHistograms(); - -#if defined(OS_WIN) - // We need to pass a named anonymous handle to shared memory over the - // command line on Windows, since the child doesn't know which of the - // handles it inherited it should open. - // PlatformFile is typedef'd to HANDLE which is typedef'd to void *. We - // basically cast the handle into an int (uintptr_t, to be exact), stringify - // the int, and pass it as a command-line flag. The child process will do - // the reverse conversions to retrieve the handle. See - // http://stackoverflow.com/a/153077 - auto uintptr_handle = - reinterpret_cast<uintptr_t>(global_->readonly_allocator_handle_); - std::string field_trial_handle = std::to_string(uintptr_handle); - cmd_line->AppendSwitchASCII(field_trial_handle_switch, field_trial_handle); -#elif defined(OS_POSIX) - // On POSIX, we dup the fd into a fixed fd kFieldTrialDescriptor, so we - // don't have to pass over the handle (it's not even the right handle - // anyways). But some browser tests don't create the allocator, so we need - // to be able to distinguish valid and invalid handles. We do that by just - // checking that the flag is set with a dummy value. - cmd_line->AppendSwitchASCII(field_trial_handle_switch, "1"); -#else -#error Unsupported OS -#endif - return; - } - - AddFeatureAndFieldTrialFlags(enable_features_switch, disable_features_switch, - cmd_line); -} - -// static FieldTrial* FieldTrialList::CreateFieldTrial( const std::string& name, const std::string& group_name) { @@ -952,20 +597,6 @@ void FieldTrialList::RemoveObserver(Observer* observer) { } // static -void FieldTrialList::OnGroupFinalized(bool is_locked, FieldTrial* field_trial) { - if (!global_) - return; - if (is_locked) { - AddToAllocatorWhileLocked(global_->field_trial_allocator_.get(), - field_trial); - } else { - AutoLock auto_lock(global_->lock_); - AddToAllocatorWhileLocked(global_->field_trial_allocator_.get(), - field_trial); - } -} - -// static void FieldTrialList::NotifyFieldTrialGroupSelection(FieldTrial* field_trial) { if (!global_) return; @@ -975,22 +606,10 @@ void FieldTrialList::NotifyFieldTrialGroupSelection(FieldTrial* field_trial) { if (field_trial->group_reported_) return; field_trial->group_reported_ = true; - - if (!field_trial->enable_field_trial_) - return; - - if (kUseSharedMemoryForFieldTrials) - ActivateFieldTrialEntryWhileLocked(field_trial); } - // Recording for stability debugging has to be done inline as a task posted - // to an observer may not get executed before a crash. - base::debug::GlobalActivityTracker* tracker = - base::debug::GlobalActivityTracker::Get(); - if (tracker) { - tracker->RecordFieldTrial(field_trial->trial_name(), - field_trial->group_name_internal()); - } + if (!field_trial->enable_field_trial_) + return; global_->observer_list_->Notify( FROM_HERE, &FieldTrialList::Observer::OnFieldTrialGroupFinalized, @@ -1006,334 +625,6 @@ size_t FieldTrialList::GetFieldTrialCount() { } // static -bool FieldTrialList::GetParamsFromSharedMemory( - FieldTrial* field_trial, - std::map<std::string, std::string>* params) { - DCHECK(global_); - // If the field trial allocator is not set up yet, then there are several - // cases: - // - We are in the browser process and the allocator has not been set up - // yet. If we got here, then we couldn't find the params in - // FieldTrialParamAssociator, so it's definitely not here. Return false. - // - Using shared memory for field trials is not enabled. If we got here, - // then there's nothing in shared memory. Return false. - // - We are in the child process and the allocator has not been set up yet. - // If this is the case, then you are calling this too early. The field trial - // allocator should get set up very early in the lifecycle. Try to see if - // you can call it after it's been set up. - AutoLock auto_lock(global_->lock_); - if (!global_->field_trial_allocator_) - return false; - - // If ref_ isn't set, then the field trial data can't be in shared memory. - if (!field_trial->ref_) - return false; - - const FieldTrial::FieldTrialEntry* entry = - global_->field_trial_allocator_->GetAsObject<FieldTrial::FieldTrialEntry>( - field_trial->ref_); - - size_t allocated_size = - global_->field_trial_allocator_->GetAllocSize(field_trial->ref_); - size_t actual_size = sizeof(FieldTrial::FieldTrialEntry) + entry->pickle_size; - if (allocated_size < actual_size) - return false; - - return entry->GetParams(params); -} - -// static -void FieldTrialList::ClearParamsFromSharedMemoryForTesting() { - if (!global_) - return; - - AutoLock auto_lock(global_->lock_); - if (!global_->field_trial_allocator_) - return; - - // To clear the params, we iterate through every item in the allocator, copy - // just the trial and group name into a newly-allocated segment and then clear - // the existing item. - FieldTrialAllocator* allocator = global_->field_trial_allocator_.get(); - FieldTrialAllocator::Iterator mem_iter(allocator); - - // List of refs to eventually be made iterable. We can't make it in the loop, - // since it would go on forever. - std::vector<FieldTrial::FieldTrialRef> new_refs; - - FieldTrial::FieldTrialRef prev_ref; - while ((prev_ref = mem_iter.GetNextOfType<FieldTrial::FieldTrialEntry>()) != - FieldTrialAllocator::kReferenceNull) { - // Get the existing field trial entry in shared memory. - const FieldTrial::FieldTrialEntry* prev_entry = - allocator->GetAsObject<FieldTrial::FieldTrialEntry>(prev_ref); - StringPiece trial_name; - StringPiece group_name; - if (!prev_entry->GetTrialAndGroupName(&trial_name, &group_name)) - continue; - - // Write a new entry, minus the params. - Pickle pickle; - pickle.WriteString(trial_name); - pickle.WriteString(group_name); - size_t total_size = sizeof(FieldTrial::FieldTrialEntry) + pickle.size(); - FieldTrial::FieldTrialEntry* new_entry = - allocator->New<FieldTrial::FieldTrialEntry>(total_size); - subtle::NoBarrier_Store(&new_entry->activated, - subtle::NoBarrier_Load(&prev_entry->activated)); - new_entry->pickle_size = pickle.size(); - - // TODO(lawrencewu): Modify base::Pickle to be able to write over a section - // in memory, so we can avoid this memcpy. - char* dst = reinterpret_cast<char*>(new_entry) + - sizeof(FieldTrial::FieldTrialEntry); - memcpy(dst, pickle.data(), pickle.size()); - - // Update the ref on the field trial and add it to the list to be made - // iterable. - FieldTrial::FieldTrialRef new_ref = allocator->GetAsReference(new_entry); - FieldTrial* trial = global_->PreLockedFind(trial_name.as_string()); - trial->ref_ = new_ref; - new_refs.push_back(new_ref); - - // Mark the existing entry as unused. - allocator->ChangeType(prev_ref, 0, - FieldTrial::FieldTrialEntry::kPersistentTypeId, - /*clear=*/false); - } - - for (const auto& ref : new_refs) { - allocator->MakeIterable(ref); - } -} - -// static -void FieldTrialList::DumpAllFieldTrialsToPersistentAllocator( - PersistentMemoryAllocator* allocator) { - if (!global_) - return; - AutoLock auto_lock(global_->lock_); - for (const auto& registered : global_->registered_) { - AddToAllocatorWhileLocked(allocator, registered.second); - } -} - -// static -std::vector<const FieldTrial::FieldTrialEntry*> -FieldTrialList::GetAllFieldTrialsFromPersistentAllocator( - PersistentMemoryAllocator const& allocator) { - std::vector<const FieldTrial::FieldTrialEntry*> entries; - FieldTrialAllocator::Iterator iter(&allocator); - const FieldTrial::FieldTrialEntry* entry; - while ((entry = iter.GetNextOfObject<FieldTrial::FieldTrialEntry>()) != - nullptr) { - entries.push_back(entry); - } - return entries; -} - -#if defined(OS_WIN) -// static -bool FieldTrialList::CreateTrialsFromHandleSwitch( - const std::string& handle_switch) { - int field_trial_handle = std::stoi(handle_switch); - HANDLE handle = reinterpret_cast<HANDLE>(field_trial_handle); - SharedMemoryHandle shm_handle(handle, GetCurrentProcId()); - return FieldTrialList::CreateTrialsFromSharedMemoryHandle(shm_handle); -} -#endif - -#if defined(OS_POSIX) && !defined(OS_NACL) -// static -bool FieldTrialList::CreateTrialsFromDescriptor(int fd_key) { - if (!kUseSharedMemoryForFieldTrials) - return false; - - if (fd_key == -1) - return false; - - int fd = GlobalDescriptors::GetInstance()->MaybeGet(fd_key); - if (fd == -1) - return false; - -#if defined(OS_MACOSX) && !defined(OS_IOS) - SharedMemoryHandle shm_handle(FileDescriptor(fd, true)); -#else - SharedMemoryHandle shm_handle(fd, true); -#endif - - bool result = FieldTrialList::CreateTrialsFromSharedMemoryHandle(shm_handle); - DCHECK(result); - return true; -} -#endif - -// static -bool FieldTrialList::CreateTrialsFromSharedMemoryHandle( - SharedMemoryHandle shm_handle) { - // shm gets deleted when it gets out of scope, but that's OK because we need - // it only for the duration of this method. - std::unique_ptr<SharedMemory> shm(new SharedMemory(shm_handle, true)); - if (!shm.get()->Map(kFieldTrialAllocationSize)) - OnOutOfMemory(kFieldTrialAllocationSize); - - return FieldTrialList::CreateTrialsFromSharedMemory(std::move(shm)); -} - -// static -bool FieldTrialList::CreateTrialsFromSharedMemory( - std::unique_ptr<SharedMemory> shm) { - global_->field_trial_allocator_.reset( - new FieldTrialAllocator(std::move(shm), 0, kAllocatorName, true)); - FieldTrialAllocator* shalloc = global_->field_trial_allocator_.get(); - FieldTrialAllocator::Iterator mem_iter(shalloc); - - const FieldTrial::FieldTrialEntry* entry; - while ((entry = mem_iter.GetNextOfObject<FieldTrial::FieldTrialEntry>()) != - nullptr) { - StringPiece trial_name; - StringPiece group_name; - if (!entry->GetTrialAndGroupName(&trial_name, &group_name)) - return false; - - // TODO(lawrencewu): Convert the API for CreateFieldTrial to take - // StringPieces. - FieldTrial* trial = - CreateFieldTrial(trial_name.as_string(), group_name.as_string()); - - trial->ref_ = mem_iter.GetAsReference(entry); - if (subtle::NoBarrier_Load(&entry->activated)) { - // Call |group()| to mark the trial as "used" and notify observers, if - // any. This is useful to ensure that field trials created in child - // processes are properly reported in crash reports. - trial->group(); - } - } - return true; -} - -// static -void FieldTrialList::InstantiateFieldTrialAllocatorIfNeeded() { - if (!global_) - return; - AutoLock auto_lock(global_->lock_); - // Create the allocator if not already created and add all existing trials. - if (global_->field_trial_allocator_ != nullptr) - return; - - SharedMemoryCreateOptions options; - options.size = kFieldTrialAllocationSize; - options.share_read_only = true; -#if defined(OS_MACOSX) && !defined(OS_IOS) - options.type = SharedMemoryHandle::POSIX; -#endif - - std::unique_ptr<SharedMemory> shm(new SharedMemory()); - if (!shm->Create(options)) - OnOutOfMemory(kFieldTrialAllocationSize); - - if (!shm->Map(kFieldTrialAllocationSize)) - OnOutOfMemory(kFieldTrialAllocationSize); - - global_->field_trial_allocator_.reset( - new FieldTrialAllocator(std::move(shm), 0, kAllocatorName, false)); - global_->field_trial_allocator_->CreateTrackingHistograms(kAllocatorName); - - // Add all existing field trials. - for (const auto& registered : global_->registered_) { - AddToAllocatorWhileLocked(global_->field_trial_allocator_.get(), - registered.second); - } - - // Add all existing features. - FeatureList::GetInstance()->AddFeaturesToAllocator( - global_->field_trial_allocator_.get()); - -#if !defined(OS_NACL) - // Set |readonly_allocator_handle_| so we can pass it to be inherited and - // via the command line. - global_->readonly_allocator_handle_ = - CreateReadOnlyHandle(global_->field_trial_allocator_.get()); -#endif -} - -// static -void FieldTrialList::AddToAllocatorWhileLocked( - PersistentMemoryAllocator* allocator, - FieldTrial* field_trial) { - // Don't do anything if the allocator hasn't been instantiated yet. - if (allocator == nullptr) - return; - - // Or if the allocator is read only, which means we are in a child process and - // shouldn't be writing to it. - if (allocator->IsReadonly()) - return; - - FieldTrial::State trial_state; - if (!field_trial->GetStateWhileLocked(&trial_state)) - return; - - // Or if we've already added it. We must check after GetState since it can - // also add to the allocator. - if (field_trial->ref_) - return; - - Pickle pickle; - if (!PickleFieldTrial(trial_state, &pickle)) { - NOTREACHED(); - return; - } - - size_t total_size = sizeof(FieldTrial::FieldTrialEntry) + pickle.size(); - FieldTrial::FieldTrialRef ref = allocator->Allocate( - total_size, FieldTrial::FieldTrialEntry::kPersistentTypeId); - if (ref == FieldTrialAllocator::kReferenceNull) { - NOTREACHED(); - return; - } - - FieldTrial::FieldTrialEntry* entry = - allocator->GetAsObject<FieldTrial::FieldTrialEntry>(ref); - subtle::NoBarrier_Store(&entry->activated, trial_state.activated); - entry->pickle_size = pickle.size(); - - // TODO(lawrencewu): Modify base::Pickle to be able to write over a section in - // memory, so we can avoid this memcpy. - char* dst = - reinterpret_cast<char*>(entry) + sizeof(FieldTrial::FieldTrialEntry); - memcpy(dst, pickle.data(), pickle.size()); - - allocator->MakeIterable(ref); - field_trial->ref_ = ref; -} - -// static -void FieldTrialList::ActivateFieldTrialEntryWhileLocked( - FieldTrial* field_trial) { - FieldTrialAllocator* allocator = global_->field_trial_allocator_.get(); - - // Check if we're in the child process and return early if so. - if (allocator && allocator->IsReadonly()) - return; - - FieldTrial::FieldTrialRef ref = field_trial->ref_; - if (ref == FieldTrialAllocator::kReferenceNull) { - // It's fine to do this even if the allocator hasn't been instantiated - // yet -- it'll just return early. - AddToAllocatorWhileLocked(global_->field_trial_allocator_.get(), - field_trial); - } else { - // It's also okay to do this even though the callee doesn't have a lock -- - // the only thing that happens on a stale read here is a slight performance - // hit from the child re-synchronizing activation state. - FieldTrial::FieldTrialEntry* entry = - allocator->GetAsObject<FieldTrial::FieldTrialEntry>(ref); - subtle::NoBarrier_Store(&entry->activated, 1); - } -} - -// static const FieldTrial::EntropyProvider* FieldTrialList::GetEntropyProviderForOneTimeRandomization() { if (!global_) { |