Chromium Code Reviews| Index: base/metrics/field_trial.cc |
| diff --git a/base/metrics/field_trial.cc b/base/metrics/field_trial.cc |
| index 7dc5cb454e55877d7e345bbe13c1a4acca4d2711..7803ab69ab30261618d2f9521046a2747af69588 100644 |
| --- a/base/metrics/field_trial.cc |
| +++ b/base/metrics/field_trial.cc |
| @@ -13,6 +13,7 @@ |
| #include "base/debug/alias.h" |
| #include "base/feature_list.h" |
| #include "base/logging.h" |
| +#include "base/metrics/field_trial_param_associator.h" |
| #include "base/pickle.h" |
| #include "base/process/memory.h" |
| #include "base/rand_util.h" |
| @@ -71,25 +72,88 @@ struct FieldTrialEntry { |
| // Size of the pickled structure, NOT the total size of this entry. |
| uint32_t size; |
| - // Calling this is only valid when the entry is initialized. That is, it |
| - // resides in shared memory and has a pickle containing the trial name and |
| - // group name following it. |
| - bool GetTrialAndGroupName(StringPiece* trial_name, |
| - StringPiece* group_name) const { |
| + // Returns an iterator over the data containing names and params. |
| + PickleIterator GetPickleIterator() const { |
| char* src = reinterpret_cast<char*>(const_cast<FieldTrialEntry*>(this)) + |
| sizeof(FieldTrialEntry); |
| Pickle pickle(src, size); |
| - PickleIterator pickle_iter(pickle); |
| + return PickleIterator(pickle); |
| + } |
| - if (!pickle_iter.ReadStringPiece(trial_name)) |
| + // Takes the iterator and writes out the first two items into |trial_name| and |
| + // |group_name|. |
| + bool ReadStringPair(PickleIterator* iter, |
| + StringPiece* trial_name, |
| + StringPiece* group_name) const { |
| + if (!iter->ReadStringPiece(trial_name)) |
| return false; |
| - if (!pickle_iter.ReadStringPiece(group_name)) |
| + if (!iter->ReadStringPiece(group_name)) |
| return false; |
| return true; |
| } |
| + |
| + // Calling this is only valid when the entry is initialized. That is, it |
| + // resides in shared memory and has a pickle containing the trial name and |
| + // group name following it. |
| + bool GetTrialAndGroupName(StringPiece* trial_name, |
| + StringPiece* group_name) const { |
| + PickleIterator iter = GetPickleIterator(); |
| + return ReadStringPair(&iter, trial_name, group_name); |
| + } |
| + |
| + // Calling this is only valid when the entry is initialized as well. Reads the |
| + // parameters following the trial and group name and stores them as key-value |
| + // mappings in |params|. |
| + bool GetParams(std::map<std::string, std::string>* params) const { |
| + PickleIterator iter = GetPickleIterator(); |
| + StringPiece tmp; |
| + 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(); |
| + } |
| + } |
| }; |
| +// 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.as_string(), trial_state.group_name.as_string(), |
| + ¶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); |
| @@ -893,6 +957,102 @@ size_t FieldTrialList::GetFieldTrialCount() { |
| return global_->registered_.size(); |
| } |
| +// 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. |
| + if (!global_->field_trial_allocator_) |
|
Alexei Svitkine (slow)
2016/11/24 21:58:48
field_trial_allocator_ shouldn't be used outside t
lawrencewu
2016/11/24 22:07:03
Done.
|
| + 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 FieldTrialEntry* entry = |
| + global_->field_trial_allocator_->GetAsObject<const FieldTrialEntry>( |
| + field_trial->ref_, kFieldTrialType); |
| + |
| + size_t allocated_size = |
| + global_->field_trial_allocator_->GetAllocSize(field_trial->ref_); |
| + size_t actual_size = sizeof(FieldTrialEntry) + entry->size; |
| + if (allocated_size < actual_size) |
| + return false; |
| + |
| + return entry->GetParams(params); |
| +} |
| + |
| +// static |
| +void FieldTrialList::ClearParamsFromSharedMemoryForTesting() { |
| + if (!global_ || !global_->field_trial_allocator_) |
|
Alexei Svitkine (slow)
2016/11/24 21:58:48
field_trial_allocator_ shouldn't be checked outsid
lawrencewu
2016/11/24 22:07:03
Done.
|
| + return; |
| + AutoLock auto_lock(global_->lock_); |
| + |
| + // 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, |
|
bcwhite
2016/11/28 21:57:41
It shouldn't. You're iterating for kFieldTrialTyp
lawrencewu
2016/11/28 22:51:26
I'm trying to make iterable the newly-allocated en
bcwhite
2016/11/30 15:05:33
Ah, of course. Good, then.
|
| + // since it would go on forever. |
| + std::vector<FieldTrial::FieldTrialRef> new_refs; |
| + |
| + FieldTrial::FieldTrialRef prev_ref; |
| + while ((prev_ref = mem_iter.GetNextOfType(kFieldTrialType)) != |
| + FieldTrialAllocator::kReferenceNull) { |
| + // Get the existing field trial entry in shared memory. |
| + const FieldTrialEntry* prev_entry = |
| + allocator->GetAsObject<const FieldTrialEntry>(prev_ref, |
| + kFieldTrialType); |
| + 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(FieldTrialEntry) + pickle.size(); |
| + FieldTrial::FieldTrialRef new_ref = |
| + allocator->Allocate(total_size, kFieldTrialType); |
| + FieldTrialEntry* new_entry = |
| + allocator->GetAsObject<FieldTrialEntry>(new_ref, kFieldTrialType); |
| + new_entry->activated = prev_entry->activated; |
| + new_entry->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(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* 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, kFieldTrialType); |
| + } |
| + |
| + for (const auto& ref : new_refs) { |
| + allocator->MakeIterable(ref); |
| + } |
| +} |
| + |
| #if defined(OS_WIN) |
| // static |
| bool FieldTrialList::CreateTrialsFromWindowsHandle(HANDLE handle) { |
| @@ -1036,14 +1196,18 @@ void FieldTrialList::AddToAllocatorWhileLocked(FieldTrial* field_trial) { |
| return; |
| Pickle pickle; |
| - pickle.WriteString(trial_state.trial_name); |
| - pickle.WriteString(trial_state.group_name); |
| + if (!PickleFieldTrial(trial_state, &pickle)) { |
| + NOTREACHED(); |
| + return; |
| + } |
| size_t total_size = sizeof(FieldTrialEntry) + pickle.size(); |
| FieldTrial::FieldTrialRef ref = |
| allocator->Allocate(total_size, kFieldTrialType); |
| - if (ref == FieldTrialAllocator::kReferenceNull) |
| + if (ref == FieldTrialAllocator::kReferenceNull) { |
| + NOTREACHED(); |
| return; |
| + } |
| FieldTrialEntry* entry = |
| allocator->GetAsObject<FieldTrialEntry>(ref, kFieldTrialType); |