|  | // Copyright 2017 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 "components/metrics/persistent_system_profile.h" | 
|  |  | 
|  | #include <set> | 
|  |  | 
|  | #include "base/atomicops.h" | 
|  | #include "base/bits.h" | 
|  | #include "base/memory/singleton.h" | 
|  | #include "base/metrics/persistent_memory_allocator.h" | 
|  | #include "base/pickle.h" | 
|  | #include "base/stl_util.h" | 
|  | #include "components/variations/active_field_trials.h" | 
|  |  | 
|  | namespace metrics { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // To provide atomic addition of records so that there is no confusion between | 
|  | // writers and readers, all of the metadata about a record is contained in a | 
|  | // structure that can be stored as a single atomic 32-bit word. | 
|  | union RecordHeader { | 
|  | struct { | 
|  | unsigned continued : 1;  // Flag indicating if there is more after this. | 
|  | unsigned type : 7;       // The type of this record. | 
|  | unsigned amount : 24;    // The amount of data to follow. | 
|  | } as_parts; | 
|  | base::subtle::Atomic32 as_atomic; | 
|  | }; | 
|  |  | 
|  | constexpr uint32_t kTypeIdSystemProfile = 0x330A7150;  // SHA1(SystemProfile) | 
|  | constexpr size_t kSystemProfileAllocSize = 4 << 10;    // 4 KiB | 
|  | constexpr size_t kMaxRecordSize = (1 << 24) - sizeof(RecordHeader); | 
|  |  | 
|  | static_assert(sizeof(RecordHeader) == sizeof(base::subtle::Atomic32), | 
|  | "bad RecordHeader size"); | 
|  |  | 
|  | // Calculate the size of a record based on the amount of data. This adds room | 
|  | // for the record header and rounds up to the next multiple of the record-header | 
|  | // size. | 
|  | size_t CalculateRecordSize(size_t data_amount) { | 
|  | return base::bits::Align(data_amount + sizeof(RecordHeader), | 
|  | sizeof(RecordHeader)); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | PersistentSystemProfile::RecordAllocator::RecordAllocator( | 
|  | base::PersistentMemoryAllocator* memory_allocator, | 
|  | size_t min_size) | 
|  | : allocator_(memory_allocator), | 
|  | has_complete_profile_(false), | 
|  | alloc_reference_(0), | 
|  | alloc_size_(0), | 
|  | end_offset_(0) { | 
|  | AddSegment(min_size); | 
|  | } | 
|  |  | 
|  | PersistentSystemProfile::RecordAllocator::RecordAllocator( | 
|  | const base::PersistentMemoryAllocator* memory_allocator) | 
|  | : allocator_( | 
|  | const_cast<base::PersistentMemoryAllocator*>(memory_allocator)), | 
|  | alloc_reference_(0), | 
|  | alloc_size_(0), | 
|  | end_offset_(0) {} | 
|  |  | 
|  | void PersistentSystemProfile::RecordAllocator::Reset() { | 
|  | // Clear the first word of all blocks so they're known to be "empty". | 
|  | alloc_reference_ = 0; | 
|  | while (NextSegment()) { | 
|  | // Get the block as a char* and cast it. It can't be fetched directly as | 
|  | // an array of RecordHeader because that's not a fundamental type and only | 
|  | // arrays of fundamental types are allowed. | 
|  | RecordHeader* header = | 
|  | reinterpret_cast<RecordHeader*>(allocator_->GetAsArray<char>( | 
|  | alloc_reference_, kTypeIdSystemProfile, sizeof(RecordHeader))); | 
|  | DCHECK(header); | 
|  | base::subtle::NoBarrier_Store(&header->as_atomic, 0); | 
|  | } | 
|  |  | 
|  | // Reset member variables. | 
|  | has_complete_profile_ = false; | 
|  | alloc_reference_ = 0; | 
|  | alloc_size_ = 0; | 
|  | end_offset_ = 0; | 
|  | } | 
|  |  | 
|  | bool PersistentSystemProfile::RecordAllocator::Write(RecordType type, | 
|  | base::StringPiece record) { | 
|  | const char* data = record.data(); | 
|  | size_t remaining_size = record.size(); | 
|  |  | 
|  | // Allocate space and write records until everything has been stored. | 
|  | do { | 
|  | if (end_offset_ == alloc_size_) { | 
|  | if (!AddSegment(remaining_size)) | 
|  | return false; | 
|  | } | 
|  | // Write out as much of the data as possible. |data| and |remaining_size| | 
|  | // are updated in place. | 
|  | if (!WriteData(type, &data, &remaining_size)) | 
|  | return false; | 
|  | } while (remaining_size > 0); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool PersistentSystemProfile::RecordAllocator::HasMoreData() const { | 
|  | if (alloc_reference_ == 0 && !NextSegment()) | 
|  | return false; | 
|  |  | 
|  | char* block = | 
|  | allocator_->GetAsArray<char>(alloc_reference_, kTypeIdSystemProfile, | 
|  | base::PersistentMemoryAllocator::kSizeAny); | 
|  | if (!block) | 
|  | return false; | 
|  |  | 
|  | RecordHeader header; | 
|  | header.as_atomic = base::subtle::Acquire_Load( | 
|  | reinterpret_cast<base::subtle::Atomic32*>(block + end_offset_)); | 
|  | return header.as_parts.type != kUnusedSpace; | 
|  | } | 
|  |  | 
|  | bool PersistentSystemProfile::RecordAllocator::Read(RecordType* type, | 
|  | std::string* record) const { | 
|  | *type = kUnusedSpace; | 
|  | record->clear(); | 
|  |  | 
|  | // Access data and read records until everything has been loaded. | 
|  | while (true) { | 
|  | if (end_offset_ == alloc_size_) { | 
|  | if (!NextSegment()) | 
|  | return false; | 
|  | } | 
|  | if (ReadData(type, record)) | 
|  | return *type != kUnusedSpace; | 
|  | } | 
|  | } | 
|  |  | 
|  | bool PersistentSystemProfile::RecordAllocator::NextSegment() const { | 
|  | base::PersistentMemoryAllocator::Iterator iter(allocator_, alloc_reference_); | 
|  | alloc_reference_ = iter.GetNextOfType(kTypeIdSystemProfile); | 
|  | alloc_size_ = allocator_->GetAllocSize(alloc_reference_); | 
|  | end_offset_ = 0; | 
|  | return alloc_reference_ != 0; | 
|  | } | 
|  |  | 
|  | bool PersistentSystemProfile::RecordAllocator::AddSegment(size_t min_size) { | 
|  | if (NextSegment()) { | 
|  | // The first record-header should have been zeroed as part of the allocation | 
|  | // or by the "reset" procedure. | 
|  | DCHECK_EQ(0, base::subtle::NoBarrier_Load( | 
|  | allocator_->GetAsArray<base::subtle::Atomic32>( | 
|  | alloc_reference_, kTypeIdSystemProfile, 1))); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | DCHECK_EQ(0U, alloc_reference_); | 
|  | DCHECK_EQ(0U, end_offset_); | 
|  |  | 
|  | size_t size = | 
|  | std::max(CalculateRecordSize(min_size), kSystemProfileAllocSize); | 
|  |  | 
|  | uint32_t ref = allocator_->Allocate(size, kTypeIdSystemProfile); | 
|  | if (!ref) | 
|  | return false;  // Allocator must be full. | 
|  | allocator_->MakeIterable(ref); | 
|  |  | 
|  | alloc_reference_ = ref; | 
|  | alloc_size_ = allocator_->GetAllocSize(ref); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool PersistentSystemProfile::RecordAllocator::WriteData(RecordType type, | 
|  | const char** data, | 
|  | size_t* data_size) { | 
|  | char* block = | 
|  | allocator_->GetAsArray<char>(alloc_reference_, kTypeIdSystemProfile, | 
|  | base::PersistentMemoryAllocator::kSizeAny); | 
|  | if (!block) | 
|  | return false;  // It's bad if there is no accessible block. | 
|  |  | 
|  | const size_t max_write_size = std::min( | 
|  | kMaxRecordSize, alloc_size_ - end_offset_ - sizeof(RecordHeader)); | 
|  | const size_t write_size = std::min(*data_size, max_write_size); | 
|  | const size_t record_size = CalculateRecordSize(write_size); | 
|  | DCHECK_LT(write_size, record_size); | 
|  |  | 
|  | // Write the data and the record header. | 
|  | RecordHeader header; | 
|  | header.as_atomic = 0; | 
|  | header.as_parts.type = type; | 
|  | header.as_parts.amount = write_size; | 
|  | header.as_parts.continued = (write_size < *data_size); | 
|  | size_t offset = end_offset_; | 
|  | end_offset_ += record_size; | 
|  | DCHECK_GE(alloc_size_, end_offset_); | 
|  | if (end_offset_ < alloc_size_) { | 
|  | // An empty record header has to be next before this one gets written. | 
|  | base::subtle::NoBarrier_Store( | 
|  | reinterpret_cast<base::subtle::Atomic32*>(block + end_offset_), 0); | 
|  | } | 
|  | memcpy(block + offset + sizeof(header), *data, write_size); | 
|  | base::subtle::Release_Store( | 
|  | reinterpret_cast<base::subtle::Atomic32*>(block + offset), | 
|  | header.as_atomic); | 
|  |  | 
|  | // Account for what was stored and prepare for follow-on records with any | 
|  | // remaining data. | 
|  | *data += write_size; | 
|  | *data_size -= write_size; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool PersistentSystemProfile::RecordAllocator::ReadData( | 
|  | RecordType* type, | 
|  | std::string* record) const { | 
|  | DCHECK_GT(alloc_size_, end_offset_); | 
|  |  | 
|  | char* block = | 
|  | allocator_->GetAsArray<char>(alloc_reference_, kTypeIdSystemProfile, | 
|  | base::PersistentMemoryAllocator::kSizeAny); | 
|  | if (!block) { | 
|  | *type = kUnusedSpace; | 
|  | return true;  // No more data. | 
|  | } | 
|  |  | 
|  | // Get and validate the record header. | 
|  | RecordHeader header; | 
|  | header.as_atomic = base::subtle::Acquire_Load( | 
|  | reinterpret_cast<base::subtle::Atomic32*>(block + end_offset_)); | 
|  | bool continued = !!header.as_parts.continued; | 
|  | if (header.as_parts.type == kUnusedSpace) { | 
|  | *type = kUnusedSpace; | 
|  | return true;  // End of all records. | 
|  | } else if (*type == kUnusedSpace) { | 
|  | *type = static_cast<RecordType>(header.as_parts.type); | 
|  | } else if (*type != header.as_parts.type) { | 
|  | NOTREACHED();  // Continuation didn't match start of record. | 
|  | *type = kUnusedSpace; | 
|  | record->clear(); | 
|  | return false; | 
|  | } | 
|  | size_t read_size = header.as_parts.amount; | 
|  | if (end_offset_ + sizeof(header) + read_size > alloc_size_) { | 
|  | NOTREACHED();  // Invalid header amount. | 
|  | *type = kUnusedSpace; | 
|  | return true;  // Don't try again. | 
|  | } | 
|  |  | 
|  | // Append the record data to the output string. | 
|  | record->append(block + end_offset_ + sizeof(header), read_size); | 
|  | end_offset_ += CalculateRecordSize(read_size); | 
|  | DCHECK_GE(alloc_size_, end_offset_); | 
|  |  | 
|  | return !continued; | 
|  | } | 
|  |  | 
|  | PersistentSystemProfile::PersistentSystemProfile() {} | 
|  |  | 
|  | PersistentSystemProfile::~PersistentSystemProfile() {} | 
|  |  | 
|  | void PersistentSystemProfile::RegisterPersistentAllocator( | 
|  | base::PersistentMemoryAllocator* memory_allocator) { | 
|  | DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | 
|  |  | 
|  | // Create and store the allocator. A |min_size| of "1" ensures that a memory | 
|  | // block is reserved now. | 
|  | RecordAllocator allocator(memory_allocator, 1); | 
|  | allocators_.push_back(std::move(allocator)); | 
|  | all_have_complete_profile_ = false; | 
|  | } | 
|  |  | 
|  | void PersistentSystemProfile::DeregisterPersistentAllocator( | 
|  | base::PersistentMemoryAllocator* memory_allocator) { | 
|  | DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | 
|  |  | 
|  | // This would be more efficient with a std::map but it's not expected that | 
|  | // allocators will get deregistered with any frequency, if at all. | 
|  | base::EraseIf(allocators_, [=](RecordAllocator& records) { | 
|  | return records.allocator() == memory_allocator; | 
|  | }); | 
|  | } | 
|  |  | 
|  | void PersistentSystemProfile::SetSystemProfile( | 
|  | const std::string& serialized_profile, | 
|  | bool complete) { | 
|  | DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | 
|  |  | 
|  | if (allocators_.empty() || serialized_profile.empty()) | 
|  | return; | 
|  |  | 
|  | for (auto& allocator : allocators_) { | 
|  | // Don't overwrite a complete profile with an incomplete one. | 
|  | if (!complete && allocator.has_complete_profile()) | 
|  | continue; | 
|  | // A full system profile always starts fresh. Incomplete keeps existing | 
|  | // records for merging. | 
|  | if (complete) | 
|  | allocator.Reset(); | 
|  | // Write out the serialized profile. | 
|  | allocator.Write(kSystemProfileProto, serialized_profile); | 
|  | // Indicate if this is a complete profile. | 
|  | if (complete) | 
|  | allocator.set_complete_profile(); | 
|  | } | 
|  |  | 
|  | if (complete) | 
|  | all_have_complete_profile_ = true; | 
|  | } | 
|  |  | 
|  | void PersistentSystemProfile::SetSystemProfile( | 
|  | const SystemProfileProto& profile, | 
|  | bool complete) { | 
|  | // Avoid serialization if passed profile is not complete and all allocators | 
|  | // already have complete ones. | 
|  | if (!complete && all_have_complete_profile_) | 
|  | return; | 
|  |  | 
|  | std::string serialized_profile; | 
|  | if (!profile.SerializeToString(&serialized_profile)) | 
|  | return; | 
|  | SetSystemProfile(serialized_profile, complete); | 
|  | } | 
|  |  | 
|  | void PersistentSystemProfile::AddFieldTrial(base::StringPiece trial, | 
|  | base::StringPiece group) { | 
|  | DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | 
|  | DCHECK(!trial.empty()); | 
|  | DCHECK(!group.empty()); | 
|  |  | 
|  | base::Pickle pickler; | 
|  | pickler.WriteString(trial); | 
|  | pickler.WriteString(group); | 
|  |  | 
|  | WriteToAll(kFieldTrialInfo, | 
|  | base::StringPiece(static_cast<const char*>(pickler.data()), | 
|  | pickler.size())); | 
|  | } | 
|  |  | 
|  | // static | 
|  | bool PersistentSystemProfile::HasSystemProfile( | 
|  | const base::PersistentMemoryAllocator& memory_allocator) { | 
|  | const RecordAllocator records(&memory_allocator); | 
|  | return records.HasMoreData(); | 
|  | } | 
|  |  | 
|  | // static | 
|  | bool PersistentSystemProfile::GetSystemProfile( | 
|  | const base::PersistentMemoryAllocator& memory_allocator, | 
|  | SystemProfileProto* system_profile) { | 
|  | const RecordAllocator records(&memory_allocator); | 
|  |  | 
|  | RecordType type; | 
|  | std::string record; | 
|  | do { | 
|  | if (!records.Read(&type, &record)) | 
|  | return false; | 
|  | } while (type != kSystemProfileProto); | 
|  |  | 
|  | if (!system_profile->ParseFromString(record)) | 
|  | return false; | 
|  |  | 
|  | MergeUpdateRecords(memory_allocator, system_profile); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // static | 
|  | void PersistentSystemProfile::MergeUpdateRecords( | 
|  | const base::PersistentMemoryAllocator& memory_allocator, | 
|  | SystemProfileProto* system_profile) { | 
|  | const RecordAllocator records(&memory_allocator); | 
|  |  | 
|  | RecordType type; | 
|  | std::string record; | 
|  | std::set<uint32_t> known_field_trial_ids; | 
|  |  | 
|  | // This is done separate from the code that gets the profile because it | 
|  | // compartmentalizes the code and makes it possible to reuse this section | 
|  | // should it be needed to merge "update" records into a new "complete" | 
|  | // system profile that somehow didn't get all the updates. | 
|  | while (records.Read(&type, &record)) { | 
|  | switch (type) { | 
|  | case kUnusedSpace: | 
|  | // These should never be returned. | 
|  | NOTREACHED(); | 
|  | break; | 
|  |  | 
|  | case kSystemProfileProto: | 
|  | // Profile was passed in; ignore this one. | 
|  | break; | 
|  |  | 
|  | case kFieldTrialInfo: { | 
|  | // Get the set of known trial IDs so duplicates don't get added. | 
|  | if (known_field_trial_ids.empty()) { | 
|  | for (int i = 0; i < system_profile->field_trial_size(); ++i) { | 
|  | known_field_trial_ids.insert( | 
|  | system_profile->field_trial(i).name_id()); | 
|  | } | 
|  | } | 
|  |  | 
|  | base::Pickle pickler(record.data(), record.size()); | 
|  | base::PickleIterator iter(pickler); | 
|  | base::StringPiece trial; | 
|  | base::StringPiece group; | 
|  | if (iter.ReadStringPiece(&trial) && iter.ReadStringPiece(&group)) { | 
|  | variations::ActiveGroupId field_ids = | 
|  | variations::MakeActiveGroupId(trial, group); | 
|  | if (!base::ContainsKey(known_field_trial_ids, field_ids.name)) { | 
|  | SystemProfileProto::FieldTrial* field_trial = | 
|  | system_profile->add_field_trial(); | 
|  | field_trial->set_name_id(field_ids.name); | 
|  | field_trial->set_group_id(field_ids.group); | 
|  | known_field_trial_ids.insert(field_ids.name); | 
|  | } | 
|  | } | 
|  | } break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void PersistentSystemProfile::WriteToAll(RecordType type, | 
|  | base::StringPiece record) { | 
|  | for (auto& allocator : allocators_) | 
|  | allocator.Write(type, record); | 
|  | } | 
|  |  | 
|  | GlobalPersistentSystemProfile* GlobalPersistentSystemProfile::GetInstance() { | 
|  | return base::Singleton< | 
|  | GlobalPersistentSystemProfile, | 
|  | base::LeakySingletonTraits<GlobalPersistentSystemProfile>>::get(); | 
|  | } | 
|  |  | 
|  | }  // namespace metrics |