123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440 |
- // 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/containers/contains.h"
- #include "base/containers/cxx20_erase.h"
- #include "base/memory/singleton.h"
- #include "base/metrics/persistent_memory_allocator.h"
- #include "base/notreached.h"
- #include "base/pickle.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::AlignUp(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;
- // System profile always starts fresh.
- 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)
- return true;
- 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::Contains(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
|