Skip to content

Commit

Permalink
Use SharedPersistentMemoryAllocator to share field trial state
Browse files Browse the repository at this point in the history
Change the method by which we share field trial state from using a
SharedMemory class to SharedPersistentMemoryAllocator. Adds this
allocator to the base::FieldTrialList singleton, so there is only one copy
of this state on the browser process vs. a copy for each process host
which is how it currently works (from
https://codereview.chromium.org/2365273004/)

BUG=653874

Review-Url: https://codereview.chromium.org/2412113002
Cr-Commit-Position: refs/heads/master@{#427378}
  • Loading branch information
lawrencewu authored and Commit bot committed Oct 25, 2016
1 parent 96b2d83 commit 0b49649
Show file tree
Hide file tree
Showing 17 changed files with 344 additions and 95 deletions.
256 changes: 214 additions & 42 deletions base/metrics/field_trial.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/process/memory.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
Expand All @@ -35,10 +36,42 @@ const char kActivationMarker = '*';
// 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
#if defined(OS_WIN)
const bool kUseSharedMemoryForFieldTrials = false;

// Constants for the field trial allocator.
const char kAllocatorName[] = "FieldTrialAllocator";
const uint32_t kFieldTrialType = 0xABA17E13 + 1; // SHA1(FieldTrialEntry) v1
#if !defined(OS_NACL)
const size_t kFieldTrialAllocationSize = 4 << 10; // 4 KiB = one page
#endif

// We create one FieldTrialEntry struct per field trial in shared memory (via
// field_trial_allocator_). It contains whether or not it's activated, and the
// offset from the start of the allocated memory to the group name. We don't
// need the offset into the trial name because that's always a fixed position
// from the start of the struct. Two strings will be appended to the end of this
// structure in allocated memory, so the result will look like this:
// ---------------------------------
// | fte | trial_name | group_name |
// ---------------------------------
// TODO(lawrencewu): Actually update the activated flag.
struct FieldTrialEntry {
bool activated;
uint32_t group_name_offset;

const char* GetTrialName() const {
const char* src =
reinterpret_cast<char*>(const_cast<FieldTrialEntry*>(this));
return src + sizeof(FieldTrialEntry);
}

const char* GetGroupName() const {
const char* src =
reinterpret_cast<char*>(const_cast<FieldTrialEntry*>(this));
return src + this->group_name_offset;
}
};

// 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);
Expand Down Expand Up @@ -124,6 +157,27 @@ bool ParseFieldTrialsString(const std::string& trials_string,
return true;
}

void AddForceFieldTrialsFlag(CommandLine* cmd_line) {
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(SharedPersistentMemoryAllocator* 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 nullptr;
return dst;
}
#endif

} // namespace

// statics
Expand Down Expand Up @@ -570,30 +624,28 @@ bool FieldTrialList::CreateTrialsFromString(

// static
void FieldTrialList::CreateTrialsFromCommandLine(
const base::CommandLine& cmd_line,
const CommandLine& cmd_line,
const char* field_trial_handle_switch) {
DCHECK(global_);

#if defined(OS_WIN)
#if defined(OS_WIN) && !defined(OS_NACL)
if (cmd_line.HasSwitch(field_trial_handle_switch)) {
std::string arg = cmd_line.GetSwitchValueASCII(field_trial_handle_switch);
size_t token = arg.find(",");
int field_trial_handle = std::stoi(arg.substr(0, token));
int field_trial_length = std::stoi(arg.substr(token + 1, arg.length()));
size_t field_trial_length = std::stoi(arg.substr(token + 1, arg.length()));

HANDLE handle = reinterpret_cast<HANDLE>(field_trial_handle);
base::SharedMemoryHandle shm_handle =
base::SharedMemoryHandle(handle, base::GetCurrentProcId());
SharedMemoryHandle shm_handle =
SharedMemoryHandle(handle, GetCurrentProcId());

// Gets deleted when it gets out of scope, but that's OK because we need it
// only for the duration of this call currently anyway.
base::SharedMemory shared_memory(shm_handle, false);
shared_memory.Map(field_trial_length);
// only for the duration of this method.
std::unique_ptr<SharedMemory> shm(new SharedMemory(shm_handle, true));
if (!shm.get()->Map(field_trial_length))
TerminateBecauseOutOfMemory(field_trial_length);

char* field_trial_state = static_cast<char*>(shared_memory.memory());
bool result = FieldTrialList::CreateTrialsFromString(
std::string(field_trial_state), std::set<std::string>());
DCHECK(result);
FieldTrialList::CreateTrialsFromSharedMemory(std::move(shm));
return;
}
#endif
Expand All @@ -606,37 +658,77 @@ void FieldTrialList::CreateTrialsFromCommandLine(
}
}

#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

// static
std::unique_ptr<base::SharedMemory> FieldTrialList::CopyFieldTrialStateToFlags(
void FieldTrialList::CreateTrialsFromSharedMemory(
std::unique_ptr<SharedMemory> shm) {
const SharedPersistentMemoryAllocator shalloc(std::move(shm), 0,
kAllocatorName, true);
PersistentMemoryAllocator::Iterator iter(&shalloc);

SharedPersistentMemoryAllocator::Reference ref;
while ((ref = iter.GetNextOfType(kFieldTrialType)) !=
SharedPersistentMemoryAllocator::kReferenceNull) {
const FieldTrialEntry* entry =
shalloc.GetAsObject<const FieldTrialEntry>(ref, kFieldTrialType);
FieldTrial* trial =
CreateFieldTrial(entry->GetTrialName(), entry->GetGroupName());

if (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();
}
}
}

// static
void FieldTrialList::CopyFieldTrialStateToFlags(
const char* field_trial_handle_switch,
base::CommandLine* cmd_line) {
std::string field_trial_states;
base::FieldTrialList::AllStatesToString(&field_trial_states);
if (!field_trial_states.empty()) {
// Use shared memory to pass the state if the feature is enabled, otherwise
// fallback to passing it via the command line as a string.
CommandLine* cmd_line) {
#if defined(OS_WIN)
if (kUseSharedMemoryForFieldTrials) {
std::unique_ptr<base::SharedMemory> shm(new base::SharedMemory());
size_t length = field_trial_states.size() + 1;
shm->CreateAndMapAnonymous(length);
memcpy(shm->memory(), field_trial_states.c_str(), length);

// HANDLE is just typedef'd to void *
auto uintptr_handle =
reinterpret_cast<std::uintptr_t>(shm->handle().GetHandle());
std::string field_trial_handle =
std::to_string(uintptr_handle) + "," + std::to_string(length);

cmd_line->AppendSwitchASCII(field_trial_handle_switch,
field_trial_handle);
return shm;
// 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_) {
AddForceFieldTrialsFlag(cmd_line);
return;
}
#endif
cmd_line->AppendSwitchASCII(switches::kForceFieldTrials,
field_trial_states);

// HANDLE is just 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_);
size_t field_trial_length =
global_->field_trial_allocator_->shared_memory()->mapped_size();
std::string field_trial_handle = std::to_string(uintptr_handle) + "," +
std::to_string(field_trial_length);

cmd_line->AppendSwitchASCII(field_trial_handle_switch, field_trial_handle);
return;
}
return std::unique_ptr<base::SharedMemory>(nullptr);
#endif

AddForceFieldTrialsFlag(cmd_line);
}

// static
Expand Down Expand Up @@ -689,16 +781,96 @@ 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 (!field_trial->enable_field_trial_)
return;

if (kUseSharedMemoryForFieldTrials) {
field_trial->AddToAllocatorWhileLocked(
global_->field_trial_allocator_.get());
}
}

global_->observer_list_->Notify(
FROM_HERE, &FieldTrialList::Observer::OnFieldTrialGroupFinalized,
field_trial->trial_name(), field_trial->group_name_internal());
}

#if !defined(OS_NACL)
// static
void FieldTrialList::InstantiateFieldTrialAllocatorIfNeeded() {
AutoLock auto_lock(global_->lock_);
// Create the allocator if not already created and add all existing trials.
if (global_->field_trial_allocator_ != nullptr)
return;

std::unique_ptr<SharedMemory> shm(new SharedMemory());
if (!shm->CreateAndMapAnonymous(kFieldTrialAllocationSize))
TerminateBecauseOutOfMemory(kFieldTrialAllocationSize);

// TODO(lawrencewu): call UpdateTrackingHistograms() when all field trials
// have been registered (perhaps in the destructor?)
global_->field_trial_allocator_.reset(new SharedPersistentMemoryAllocator(
std::move(shm), 0, kAllocatorName, false));
global_->field_trial_allocator_->CreateTrackingHistograms(kAllocatorName);

// Add all existing field trials.
for (const auto& registered : global_->registered_) {
registered.second->AddToAllocatorWhileLocked(
global_->field_trial_allocator_.get());
}

#if defined(OS_WIN)
// 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
}
#endif

void FieldTrial::AddToAllocatorWhileLocked(
SharedPersistentMemoryAllocator* allocator) {
// Don't do anything if the allocator hasn't been instantiated yet.
if (allocator == nullptr)
return;

State trial_state;
if (!GetState(&trial_state))
return;

size_t trial_name_size = trial_state.trial_name.size() + 1;
size_t group_name_size = trial_state.group_name.size() + 1;
size_t size = sizeof(FieldTrialEntry) + trial_name_size + group_name_size;

// Allocate just enough memory to fit the FieldTrialEntry struct, trial name,
// and group name.
SharedPersistentMemoryAllocator::Reference ref =
allocator->Allocate(size, kFieldTrialType);
if (ref == SharedPersistentMemoryAllocator::kReferenceNull)
return;

// Calculate offsets into the shared memory segment.
FieldTrialEntry* entry =
allocator->GetAsObject<FieldTrialEntry>(ref, kFieldTrialType);
uint32_t trial_name_offset = sizeof(FieldTrialEntry);
uint32_t group_name_offset = trial_name_offset + trial_name_size;

// Copy over the data.
entry->activated = trial_state.activated;
entry->group_name_offset = group_name_offset;
char* trial_name = reinterpret_cast<char*>(entry) + trial_name_offset;
char* group_name = reinterpret_cast<char*>(entry) + group_name_offset;
memcpy(trial_name, trial_state.trial_name.data(), trial_name_size);
memcpy(group_name, trial_state.group_name.data(), group_name_size);

// Null terminate the strings.
trial_name[trial_state.trial_name.size()] = '\0';
group_name[trial_state.group_name.size()] = '\0';

allocator->MakeIterable(ref);
}

// static
size_t FieldTrialList::GetFieldTrialCount() {
if (!global_)
Expand Down
Loading

0 comments on commit 0b49649

Please sign in to comment.