Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(726)

Side by Side Diff: base/metrics/field_trial.cc

Issue 2412113002: Use SharedPersistentMemoryAllocator to share field trial state (Closed)
Patch Set: add some documentation Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "base/metrics/field_trial.h" 5 #include "base/metrics/field_trial.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 #include <utility> 8 #include <utility>
9 9
10 #include "base/base_switches.h" 10 #include "base/base_switches.h"
(...skipping 21 matching lines...) Expand all
32 const char kActivationMarker = '*'; 32 const char kActivationMarker = '*';
33 33
34 // Use shared memory to communicate field trial (experiment) state. Set to false 34 // Use shared memory to communicate field trial (experiment) state. Set to false
35 // for now while the implementation is fleshed out (e.g. data format, single 35 // for now while the implementation is fleshed out (e.g. data format, single
36 // shared memory segment). See https://codereview.chromium.org/2365273004/ and 36 // shared memory segment). See https://codereview.chromium.org/2365273004/ and
37 // crbug.com/653874 37 // crbug.com/653874
38 #if defined(OS_WIN) 38 #if defined(OS_WIN)
39 const bool kUseSharedMemoryForFieldTrials = false; 39 const bool kUseSharedMemoryForFieldTrials = false;
40 #endif 40 #endif
41 41
42 const base::StringPiece kAllocatorName = "field_trial_allocator";
Alexei Svitkine (slow) 2016/10/12 20:41:00 const char kAllocatorName[] =
lawrencewu 2016/10/14 04:54:09 Done.
43 const uint64_t kAllocatorId = 1337;
bcwhite 2016/10/13 14:28:02 If you don't care about this, you can always pass
lawrencewu 2016/10/14 04:54:09 Done.
44 const uint64_t kFieldTrialType = 7331;
bcwhite 2016/10/13 14:28:02 Allocator ID is 64 bits but type IDs are only 32 b
lawrencewu 2016/10/14 04:54:09 Changed to the sha1sum + 1.
45
46 // We create one FieldTrialEntry struct per field trial in shared memory (via
47 // field_trial_allocator). It contains whether or not it's activated, and the
48 // offset from the start of the allocated memory to the group name. We don't
49 // need the offset into the trial name because that's always a fixed position
50 // from the start of the struct.
bcwhite 2016/10/13 14:28:02 Document that two strings are appended to the end
lawrencewu 2016/10/14 04:54:09 I added a comment. While adding the name[1] member
bcwhite 2016/10/14 13:56:56 You could simply call it "strings" instead of "nam
lawrencewu 2016/10/19 16:21:28 Ack, will drop.
51 struct FieldTrialEntry {
52 bool activated;
53 uint32_t group_name_offset;
54 };
55
42 // Created a time value based on |year|, |month| and |day_of_month| parameters. 56 // Created a time value based on |year|, |month| and |day_of_month| parameters.
43 Time CreateTimeFromParams(int year, int month, int day_of_month) { 57 Time CreateTimeFromParams(int year, int month, int day_of_month) {
44 DCHECK_GT(year, 1970); 58 DCHECK_GT(year, 1970);
45 DCHECK_GT(month, 0); 59 DCHECK_GT(month, 0);
46 DCHECK_LT(month, 13); 60 DCHECK_LT(month, 13);
47 DCHECK_GT(day_of_month, 0); 61 DCHECK_GT(day_of_month, 0);
48 DCHECK_LT(day_of_month, 32); 62 DCHECK_LT(day_of_month, 32);
49 63
50 Time::Exploded exploded; 64 Time::Exploded exploded;
51 exploded.year = year; 65 exploded.year = year;
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after
125 } 139 }
126 140
127 } // namespace 141 } // namespace
128 142
129 // statics 143 // statics
130 const int FieldTrial::kNotFinalized = -1; 144 const int FieldTrial::kNotFinalized = -1;
131 const int FieldTrial::kDefaultGroupNumber = 0; 145 const int FieldTrial::kDefaultGroupNumber = 0;
132 bool FieldTrial::enable_benchmarking_ = false; 146 bool FieldTrial::enable_benchmarking_ = false;
133 147
134 int FieldTrialList::kNoExpirationYear = 0; 148 int FieldTrialList::kNoExpirationYear = 0;
149 base::SharedPersistentMemoryAllocator* FieldTrialList::field_trial_allocator =
150 nullptr;
135 151
136 //------------------------------------------------------------------------------ 152 //------------------------------------------------------------------------------
137 // FieldTrial methods and members. 153 // FieldTrial methods and members.
138 154
139 FieldTrial::EntropyProvider::~EntropyProvider() { 155 FieldTrial::EntropyProvider::~EntropyProvider() {
140 } 156 }
141 157
142 FieldTrial::State::State() : activated(false) {} 158 FieldTrial::State::State() : activated(false) {}
143 159
144 FieldTrial::State::State(const State& other) = default; 160 FieldTrial::State::State(const State& other) = default;
(...skipping 425 matching lines...) Expand 10 before | Expand all | Expand 10 after
570 586
571 // static 587 // static
572 void FieldTrialList::CreateTrialsFromCommandLine( 588 void FieldTrialList::CreateTrialsFromCommandLine(
573 const base::CommandLine& cmd_line, 589 const base::CommandLine& cmd_line,
574 const char* field_trial_handle_switch) { 590 const char* field_trial_handle_switch) {
575 DCHECK(global_); 591 DCHECK(global_);
576 592
577 #if defined(OS_WIN) 593 #if defined(OS_WIN)
578 if (cmd_line.HasSwitch(field_trial_handle_switch)) { 594 if (cmd_line.HasSwitch(field_trial_handle_switch)) {
579 std::string arg = cmd_line.GetSwitchValueASCII(field_trial_handle_switch); 595 std::string arg = cmd_line.GetSwitchValueASCII(field_trial_handle_switch);
580 size_t token = arg.find(","); 596 int field_trial_handle = std::stoi(arg);
581 int field_trial_handle = std::stoi(arg.substr(0, token));
582 int field_trial_length = std::stoi(arg.substr(token + 1, arg.length()));
583 597
584 HANDLE handle = reinterpret_cast<HANDLE>(field_trial_handle); 598 HANDLE handle = reinterpret_cast<HANDLE>(field_trial_handle);
585 base::SharedMemoryHandle shm_handle = 599 base::SharedMemoryHandle shm_handle =
586 base::SharedMemoryHandle(handle, base::GetCurrentProcId()); 600 base::SharedMemoryHandle(handle, base::GetCurrentProcId());
587 601
588 // Gets deleted when it gets out of scope, but that's OK because we need it 602 // Gets deleted when it gets out of scope, but that's OK because we need it
589 // only for the duration of this call currently anyway. 603 // only for the duration of this call currently anyway.
bcwhite 2016/10/13 14:28:02 ... only for the duration of this method.
lawrencewu 2016/10/14 04:54:09 Fixed.
590 base::SharedMemory shared_memory(shm_handle, false); 604 std::unique_ptr<base::SharedMemory> shm(
591 shared_memory.Map(field_trial_length); 605 new base::SharedMemory(shm_handle, false));
606 shm.get()->Map(2 << 12); // 4 KiB = one page
bcwhite 2016/10/13 14:28:02 It looks like the comment is describing the consta
lawrencewu 2016/10/14 04:54:09 Whoops, that should really just be 4KiB, so the nu
592 607
593 char* field_trial_state = static_cast<char*>(shared_memory.memory()); 608 FieldTrialList::CreateTrialsFromSharedMemory(std::move(shm));
594 bool result = FieldTrialList::CreateTrialsFromString(
595 std::string(field_trial_state), std::set<std::string>());
596 DCHECK(result);
597 return; 609 return;
598 } 610 }
599 #endif 611 #endif
600 612
601 if (cmd_line.HasSwitch(switches::kForceFieldTrials)) { 613 if (cmd_line.HasSwitch(switches::kForceFieldTrials)) {
602 bool result = FieldTrialList::CreateTrialsFromString( 614 bool result = FieldTrialList::CreateTrialsFromString(
603 cmd_line.GetSwitchValueASCII(switches::kForceFieldTrials), 615 cmd_line.GetSwitchValueASCII(switches::kForceFieldTrials),
604 std::set<std::string>()); 616 std::set<std::string>());
605 DCHECK(result); 617 DCHECK(result);
606 } 618 }
607 } 619 }
608 620
609 // static 621 // static
610 std::unique_ptr<base::SharedMemory> FieldTrialList::CopyFieldTrialStateToFlags( 622 void FieldTrialList::CreateTrialsFromSharedMemory(
623 std::unique_ptr<base::SharedMemory> shm) {
624 base::SharedPersistentMemoryAllocator shalloc(std::move(shm), kAllocatorId,
625 kAllocatorName, true);
bcwhite 2016/10/13 14:28:02 Since you're declaring the allocator as read-only,
bcwhite 2016/10/13 18:41:15 Actually, if you make this a "const" object then y
lawrencewu 2016/10/14 04:54:09 Ack, will ignore this.
bcwhite 2016/10/14 13:56:56 No, seriously. Make it "const". This case is exa
lawrencewu 2016/10/19 16:21:28 Oh, my bad, I misunderstood you (thought your seco
626 base::PersistentMemoryAllocator::Iterator iter(&shalloc);
627
628 uint32_t type;
629 base::SharedPersistentMemoryAllocator::Reference ref = iter.GetNext(&type);
bcwhite 2016/10/13 14:28:02 This will return a reference of any type but below
lawrencewu 2016/10/14 04:54:09 Done.
630 while (ref != base::SharedPersistentMemoryAllocator::kReferenceNull) {
bcwhite 2016/10/13 14:28:02 How about: while ((ref = iter.GetNextOfType(kFiel
lawrencewu 2016/10/14 04:54:09 Very nice, done.
631 char* src = shalloc.GetAsObject<char>(ref, kFieldTrialType);
632
633 FieldTrialEntry fte;
634 memcpy(&fte, src, sizeof(FieldTrialEntry));
Alexei Svitkine (slow) 2016/10/12 20:41:00 This memcpy is unnecessary. You can just cast src
lawrencewu 2016/10/14 04:54:09 Done.
635
636 char* trial_name = src + sizeof(FieldTrialEntry);
637 char* group_name = src + fte.group_name_offset;
Alexei Svitkine (slow) 2016/10/12 20:41:00 Can you make GetTrialName() and GetGroupName() acc
lawrencewu 2016/10/14 04:54:09 Done.
638
639 FieldTrial* trial = CreateFieldTrial(trial_name, group_name);
640 if (fte.activated) {
641 // Call |group()| to mark the trial as "used" and notify observers, if
642 // any. This is useful to ensure that field trials created in child
643 // processes are properly reported in crash reports.
644 trial->group();
645 }
646
647 ref = iter.GetNext(&type);
648 }
649 }
650
651 // static
652 void FieldTrialList::CopyFieldTrialStateToFlags(
611 const char* field_trial_handle_switch, 653 const char* field_trial_handle_switch,
612 base::CommandLine* cmd_line) { 654 base::CommandLine* cmd_line) {
655 // Use shared memory to pass the state if the feature is enabled, otherwise
bcwhite 2016/10/13 14:28:02 Move comment inside #ifdef and fix indent.
lawrencewu 2016/10/14 04:54:09 Done.
656 // fallback to passing it via the command line as a string.
657 #if defined(OS_WIN)
658 if (kUseSharedMemoryForFieldTrials) {
659 base::SharedMemory* shm = field_trial_allocator->shared_memory();
660
661 // HANDLE is just typedef'd to void *
662 auto uintptr_handle =
663 reinterpret_cast<std::uintptr_t>(shm->handle().GetHandle());
bcwhite 2016/10/13 14:28:02 Is std:: required for uintptr_t?
lawrencewu 2016/10/14 04:54:09 Looks like no. Omitted.
664 std::string field_trial_handle = std::to_string(uintptr_handle);
665
666 cmd_line->AppendSwitchASCII(field_trial_handle_switch, field_trial_handle);
bcwhite 2016/10/13 14:28:02 So... We can take a local void*, convert it to an
lawrencewu 2016/10/14 04:54:09 Yup, pretty hacky...added a comment and link to th
667 return;
668 }
669 #endif
670
613 std::string field_trial_states; 671 std::string field_trial_states;
614 base::FieldTrialList::AllStatesToString(&field_trial_states); 672 base::FieldTrialList::AllStatesToString(&field_trial_states);
615 if (!field_trial_states.empty()) { 673 if (!field_trial_states.empty()) {
616 // Use shared memory to pass the state if the feature is enabled, otherwise
617 // fallback to passing it via the command line as a string.
618 #if defined(OS_WIN)
619 if (kUseSharedMemoryForFieldTrials) {
620 std::unique_ptr<base::SharedMemory> shm(new base::SharedMemory());
621 size_t length = field_trial_states.size() + 1;
622 shm->CreateAndMapAnonymous(length);
623 memcpy(shm->memory(), field_trial_states.c_str(), length);
624
625 // HANDLE is just typedef'd to void *
626 auto uintptr_handle =
627 reinterpret_cast<std::uintptr_t>(shm->handle().GetHandle());
628 std::string field_trial_handle =
629 std::to_string(uintptr_handle) + "," + std::to_string(length);
630
631 cmd_line->AppendSwitchASCII(field_trial_handle_switch,
632 field_trial_handle);
633 return shm;
634 }
635 #endif
636 cmd_line->AppendSwitchASCII(switches::kForceFieldTrials, 674 cmd_line->AppendSwitchASCII(switches::kForceFieldTrials,
637 field_trial_states); 675 field_trial_states);
638 } 676 }
639 return std::unique_ptr<base::SharedMemory>(nullptr);
640 } 677 }
641 678
642 // static 679 // static
643 FieldTrial* FieldTrialList::CreateFieldTrial( 680 FieldTrial* FieldTrialList::CreateFieldTrial(
644 const std::string& name, 681 const std::string& name,
645 const std::string& group_name) { 682 const std::string& group_name) {
646 DCHECK(global_); 683 DCHECK(global_);
647 DCHECK_GE(name.size(), 0u); 684 DCHECK_GE(name.size(), 0u);
648 DCHECK_GE(group_name.size(), 0u); 685 DCHECK_GE(group_name.size(), 0u);
649 if (name.empty() || group_name.empty() || !global_) 686 if (name.empty() || group_name.empty() || !global_)
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
687 { 724 {
688 AutoLock auto_lock(global_->lock_); 725 AutoLock auto_lock(global_->lock_);
689 if (field_trial->group_reported_) 726 if (field_trial->group_reported_)
690 return; 727 return;
691 field_trial->group_reported_ = true; 728 field_trial->group_reported_ = true;
692 } 729 }
693 730
694 if (!field_trial->enable_field_trial_) 731 if (!field_trial->enable_field_trial_)
695 return; 732 return;
696 733
734 #if defined(OS_WIN)
bcwhite 2016/10/13 14:28:02 This isn't windows-specific code... Can we omit t
lawrencewu 2016/10/14 04:54:09 Yeah, we can do that. Omitted.
735 if (kUseSharedMemoryForFieldTrials) {
736 FieldTrialList::UpdateFieldTrialAllocator(field_trial);
737 }
738 #endif
739
697 global_->observer_list_->Notify( 740 global_->observer_list_->Notify(
698 FROM_HERE, &FieldTrialList::Observer::OnFieldTrialGroupFinalized, 741 FROM_HERE, &FieldTrialList::Observer::OnFieldTrialGroupFinalized,
699 field_trial->trial_name(), field_trial->group_name_internal()); 742 field_trial->trial_name(), field_trial->group_name_internal());
700 } 743 }
701 744
702 // static 745 // static
746 void FieldTrialList::UpdateFieldTrialAllocator(FieldTrial* field_trial) {
747 // Create the allocator if not already created.
748 if (field_trial_allocator == nullptr) {
bcwhite 2016/10/13 14:28:02 Are all calls to this method guaranteed to be on t
lawrencewu 2016/10/14 04:54:09 I'm not too sure about this. I'll do some investig
lawrencewu 2016/10/19 16:21:28 Turns out that it is not guaranteed to be thread-s
749 std::unique_ptr<base::SharedMemory> shm(new base::SharedMemory());
750 shm->CreateAndMapAnonymous(2 << 12); // 4 KiB = one page
751
752 field_trial_allocator = new base::SharedPersistentMemoryAllocator(
753 std::move(shm), kAllocatorId, kAllocatorName, false);
754 }
755
756 // We allocate just enough memory to fit the FTE struct, which contains
757 // whether or not the field trial is activated and the offset from the start
758 // of the struct to the group name.
759 // By the end of this function, the piece of allocated memory will look like
760 // this:
761 // ---------------------------------
762 // | fte | trial_name | group_name |
763 // ---------------------------------
764 // with fte pointing ^
765 FieldTrial::State trial_state;
766 field_trial->GetState(&trial_state);
767
768 std::string trial_name;
769 trial_state.trial_name.CopyToString(&trial_name);
770 size_t trial_name_size = trial_name.size() + 1;
771
772 std::string group_name;
773 trial_state.group_name.CopyToString(&group_name);
774 size_t group_name_size = group_name.size() + 1;
775
776 uint32_t trial_name_offset = sizeof(FieldTrialEntry);
777 uint32_t group_name_offset = trial_name_offset + trial_name_size;
778 size_t size = sizeof(FieldTrialEntry) + trial_name_size + group_name_size;
779
780 base::SharedPersistentMemoryAllocator::Reference ref =
781 field_trial_allocator->Allocate(size, kFieldTrialType);
782 field_trial_allocator->MakeIterable(ref);
bcwhite 2016/10/13 14:28:02 Move this to the end. Don't make it iterable unti
lawrencewu 2016/10/14 04:54:09 Done.
783
784 char* dst = field_trial_allocator->GetAsObject<char>(ref, kFieldTrialType);
785 FieldTrialEntry fte = {trial_state.activated, group_name_offset};
786 memcpy(dst, &fte, sizeof(FieldTrialEntry));
787 memcpy(dst + trial_name_offset, trial_name.c_str(), trial_name_size);
788 memcpy(dst + group_name_offset, group_name.c_str(), group_name_size);
789 }
790
791 // static
703 size_t FieldTrialList::GetFieldTrialCount() { 792 size_t FieldTrialList::GetFieldTrialCount() {
704 if (!global_) 793 if (!global_)
705 return 0; 794 return 0;
706 AutoLock auto_lock(global_->lock_); 795 AutoLock auto_lock(global_->lock_);
707 return global_->registered_.size(); 796 return global_->registered_.size();
708 } 797 }
709 798
710 // static 799 // static
711 const FieldTrial::EntropyProvider* 800 const FieldTrial::EntropyProvider*
712 FieldTrialList::GetEntropyProviderForOneTimeRandomization() { 801 FieldTrialList::GetEntropyProviderForOneTimeRandomization() {
(...skipping 19 matching lines...) Expand all
732 return; 821 return;
733 } 822 }
734 AutoLock auto_lock(global_->lock_); 823 AutoLock auto_lock(global_->lock_);
735 CHECK(!global_->PreLockedFind(trial->trial_name())) << trial->trial_name(); 824 CHECK(!global_->PreLockedFind(trial->trial_name())) << trial->trial_name();
736 trial->AddRef(); 825 trial->AddRef();
737 trial->SetTrialRegistered(); 826 trial->SetTrialRegistered();
738 global_->registered_[trial->trial_name()] = trial; 827 global_->registered_[trial->trial_name()] = trial;
739 } 828 }
740 829
741 } // namespace base 830 } // namespace base
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698