1 // Copyright 2017 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/metrics/persistent_system_profile.h"
9 #include "base/atomicops.h"
10 #include "base/bits.h"
11 #include "base/containers/contains.h"
12 #include "base/containers/cxx20_erase.h"
13 #include "base/debug/crash_logging.h"
14 #include "base/memory/singleton.h"
15 #include "base/metrics/persistent_memory_allocator.h"
16 #include "base/notreached.h"
17 #include "base/pickle.h"
18 #include "components/variations/active_field_trials.h"
24 // To provide atomic addition of records so that there is no confusion between
25 // writers and readers, all of the metadata about a record is contained in a
26 // structure that can be stored as a single atomic 32-bit word.
29 unsigned continued : 1; // Flag indicating if there is more after this.
30 unsigned type : 7; // The type of this record.
31 unsigned amount : 24; // The amount of data to follow.
33 base::subtle::Atomic32 as_atomic;
36 constexpr uint32_t kTypeIdSystemProfile = 0x330A7150; // SHA1(SystemProfile)
37 constexpr size_t kSystemProfileAllocSize = 4 << 10; // 4 KiB
38 constexpr size_t kMaxRecordSize = (1 << 24) - sizeof(RecordHeader);
39 constexpr char kFieldTrialDeletionSentinel[] = "";
41 static_assert(sizeof(RecordHeader) == sizeof(base::subtle::Atomic32),
42 "bad RecordHeader size");
44 // Calculate the size of a record based on the amount of data. This adds room
45 // for the record header and rounds up to the next multiple of the record-header
47 size_t CalculateRecordSize(size_t data_amount) {
48 return base::bits::AlignUp(data_amount + sizeof(RecordHeader),
49 sizeof(RecordHeader));
54 PersistentSystemProfile::RecordAllocator::RecordAllocator(
55 base::PersistentMemoryAllocator* memory_allocator,
57 : allocator_(memory_allocator),
58 has_complete_profile_(false),
65 PersistentSystemProfile::RecordAllocator::RecordAllocator(
66 const base::PersistentMemoryAllocator* memory_allocator)
68 const_cast<base::PersistentMemoryAllocator*>(memory_allocator)),
73 void PersistentSystemProfile::RecordAllocator::Reset() {
74 // Clear the first word of all blocks so they're known to be "empty".
76 while (NextSegment()) {
77 // Get the block as a char* and cast it. It can't be fetched directly as
78 // an array of RecordHeader because that's not a fundamental type and only
79 // arrays of fundamental types are allowed.
80 RecordHeader* header =
81 reinterpret_cast<RecordHeader*>(allocator_->GetAsArray<char>(
82 alloc_reference_, kTypeIdSystemProfile, sizeof(RecordHeader)));
84 base::subtle::NoBarrier_Store(&header->as_atomic, 0);
87 // Reset member variables.
88 has_complete_profile_ = false;
94 bool PersistentSystemProfile::RecordAllocator::Write(RecordType type,
95 base::StringPiece record) {
96 const char* data = record.data();
97 size_t remaining_size = record.size();
99 // Allocate space and write records until everything has been stored.
101 if (end_offset_ == alloc_size_) {
102 if (!AddSegment(remaining_size))
105 // Write out as much of the data as possible. |data| and |remaining_size|
106 // are updated in place.
107 if (!WriteData(type, &data, &remaining_size))
109 } while (remaining_size > 0);
114 bool PersistentSystemProfile::RecordAllocator::HasMoreData() const {
115 if (alloc_reference_ == 0 && !NextSegment())
119 allocator_->GetAsArray<char>(alloc_reference_, kTypeIdSystemProfile,
120 base::PersistentMemoryAllocator::kSizeAny);
125 header.as_atomic = base::subtle::Acquire_Load(
126 reinterpret_cast<base::subtle::Atomic32*>(block + end_offset_));
127 return header.as_parts.type != kUnusedSpace;
130 bool PersistentSystemProfile::RecordAllocator::Read(RecordType* type,
131 std::string* record) const {
132 *type = kUnusedSpace;
135 // Access data and read records until everything has been loaded.
137 if (end_offset_ == alloc_size_) {
141 if (ReadData(type, record))
142 return *type != kUnusedSpace;
146 bool PersistentSystemProfile::RecordAllocator::NextSegment() const {
147 base::PersistentMemoryAllocator::Iterator iter(allocator_, alloc_reference_);
148 alloc_reference_ = iter.GetNextOfType(kTypeIdSystemProfile);
149 alloc_size_ = allocator_->GetAllocSize(alloc_reference_);
151 return alloc_reference_ != 0;
154 bool PersistentSystemProfile::RecordAllocator::AddSegment(size_t min_size) {
156 // The first record-header should have been zeroed as part of the allocation
157 // or by the "reset" procedure.
158 DCHECK_EQ(0, base::subtle::NoBarrier_Load(
159 allocator_->GetAsArray<base::subtle::Atomic32>(
160 alloc_reference_, kTypeIdSystemProfile, 1)));
164 DCHECK_EQ(0U, alloc_reference_);
165 DCHECK_EQ(0U, end_offset_);
168 std::max(CalculateRecordSize(min_size), kSystemProfileAllocSize);
170 uint32_t ref = allocator_->Allocate(size, kTypeIdSystemProfile);
172 return false; // Allocator must be full.
173 allocator_->MakeIterable(ref);
175 alloc_reference_ = ref;
176 alloc_size_ = allocator_->GetAllocSize(ref);
180 bool PersistentSystemProfile::RecordAllocator::WriteData(RecordType type,
184 allocator_->GetAsArray<char>(alloc_reference_, kTypeIdSystemProfile,
185 base::PersistentMemoryAllocator::kSizeAny);
187 return false; // It's bad if there is no accessible block.
189 const size_t max_write_size = std::min(
190 kMaxRecordSize, alloc_size_ - end_offset_ - sizeof(RecordHeader));
191 const size_t write_size = std::min(*data_size, max_write_size);
192 const size_t record_size = CalculateRecordSize(write_size);
193 DCHECK_LT(write_size, record_size);
195 // Write the data and the record header.
197 header.as_atomic = 0;
198 header.as_parts.type = type;
199 header.as_parts.amount = write_size;
200 header.as_parts.continued = (write_size < *data_size);
201 size_t offset = end_offset_;
202 end_offset_ += record_size;
203 DCHECK_GE(alloc_size_, end_offset_);
204 if (end_offset_ < alloc_size_) {
205 // An empty record header has to be next before this one gets written.
206 base::subtle::NoBarrier_Store(
207 reinterpret_cast<base::subtle::Atomic32*>(block + end_offset_), 0);
209 memcpy(block + offset + sizeof(header), *data, write_size);
210 base::subtle::Release_Store(
211 reinterpret_cast<base::subtle::Atomic32*>(block + offset),
214 // Account for what was stored and prepare for follow-on records with any
217 *data_size -= write_size;
222 bool PersistentSystemProfile::RecordAllocator::ReadData(
224 std::string* record) const {
225 DCHECK_GT(alloc_size_, end_offset_);
228 allocator_->GetAsArray<char>(alloc_reference_, kTypeIdSystemProfile,
229 base::PersistentMemoryAllocator::kSizeAny);
231 *type = kUnusedSpace;
232 return true; // No more data.
235 // Get and validate the record header.
237 header.as_atomic = base::subtle::Acquire_Load(
238 reinterpret_cast<base::subtle::Atomic32*>(block + end_offset_));
239 bool continued = !!header.as_parts.continued;
240 if (header.as_parts.type == kUnusedSpace) {
241 *type = kUnusedSpace;
242 return true; // End of all records.
243 } else if (*type == kUnusedSpace) {
244 *type = static_cast<RecordType>(header.as_parts.type);
245 } else if (*type != header.as_parts.type) {
246 NOTREACHED(); // Continuation didn't match start of record.
247 *type = kUnusedSpace;
251 size_t read_size = header.as_parts.amount;
252 if (end_offset_ + sizeof(header) + read_size > alloc_size_) {
253 #if !BUILDFLAG(IS_NACL)
254 // TODO(crbug/1432981): Remove these. They are used to investigate
255 // unexpected failures.
256 SCOPED_CRASH_KEY_NUMBER("PersistentSystemProfile", "end_offset_",
258 SCOPED_CRASH_KEY_NUMBER("PersistentSystemProfile", "read_size", read_size);
259 SCOPED_CRASH_KEY_NUMBER("PersistentSystemProfile", "alloc_size_",
261 #endif // !BUILDFLAG(IS_NACL)
263 NOTREACHED(); // Invalid header amount.
264 *type = kUnusedSpace;
265 return true; // Don't try again.
268 // Append the record data to the output string.
269 record->append(block + end_offset_ + sizeof(header), read_size);
270 end_offset_ += CalculateRecordSize(read_size);
271 DCHECK_GE(alloc_size_, end_offset_);
276 PersistentSystemProfile::PersistentSystemProfile() {}
278 PersistentSystemProfile::~PersistentSystemProfile() {}
280 void PersistentSystemProfile::RegisterPersistentAllocator(
281 base::PersistentMemoryAllocator* memory_allocator) {
282 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
284 // Create and store the allocator. A |min_size| of "1" ensures that a memory
285 // block is reserved now.
286 RecordAllocator allocator(memory_allocator, 1);
287 allocators_.push_back(std::move(allocator));
288 all_have_complete_profile_ = false;
291 void PersistentSystemProfile::DeregisterPersistentAllocator(
292 base::PersistentMemoryAllocator* memory_allocator) {
293 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
295 // This would be more efficient with a std::map but it's not expected that
296 // allocators will get deregistered with any frequency, if at all.
297 base::EraseIf(allocators_, [=](RecordAllocator& records) {
298 return records.allocator() == memory_allocator;
302 void PersistentSystemProfile::SetSystemProfile(
303 const std::string& serialized_profile,
305 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
307 if (allocators_.empty() || serialized_profile.empty())
310 for (auto& allocator : allocators_) {
311 // Don't overwrite a complete profile with an incomplete one.
312 if (!complete && allocator.has_complete_profile())
314 // System profile always starts fresh.
316 // Write out the serialized profile.
317 allocator.Write(kSystemProfileProto, serialized_profile);
318 // Indicate if this is a complete profile.
320 allocator.set_complete_profile();
324 all_have_complete_profile_ = true;
327 void PersistentSystemProfile::SetSystemProfile(
328 const SystemProfileProto& profile,
330 // Avoid serialization if passed profile is not complete and all allocators
331 // already have complete ones.
332 if (!complete && all_have_complete_profile_)
335 std::string serialized_profile;
336 if (!profile.SerializeToString(&serialized_profile))
338 SetSystemProfile(serialized_profile, complete);
341 void PersistentSystemProfile::AddFieldTrial(base::StringPiece trial,
342 base::StringPiece group) {
343 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
344 DCHECK(!trial.empty());
346 base::Pickle pickler;
347 pickler.WriteString(trial);
348 pickler.WriteString(group);
350 WriteToAll(kFieldTrialInfo,
351 base::StringPiece(pickler.data_as_char(), pickler.size()));
354 void PersistentSystemProfile::RemoveFieldTrial(base::StringPiece trial) {
355 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
356 DCHECK(!trial.empty());
358 base::Pickle pickler;
359 pickler.WriteString(trial);
360 pickler.WriteString(kFieldTrialDeletionSentinel);
362 WriteToAll(kFieldTrialInfo,
363 base::StringPiece(pickler.data_as_char(), pickler.size()));
366 bool PersistentSystemProfile::HasSystemProfile(
367 const base::PersistentMemoryAllocator& memory_allocator) {
368 const RecordAllocator records(&memory_allocator);
369 return records.HasMoreData();
373 bool PersistentSystemProfile::GetSystemProfile(
374 const base::PersistentMemoryAllocator& memory_allocator,
375 SystemProfileProto* system_profile) {
376 const RecordAllocator records(&memory_allocator);
381 if (!records.Read(&type, &record))
383 } while (type != kSystemProfileProto);
388 if (!system_profile->ParseFromString(record))
391 MergeUpdateRecords(memory_allocator, system_profile);
396 void PersistentSystemProfile::MergeUpdateRecords(
397 const base::PersistentMemoryAllocator& memory_allocator,
398 SystemProfileProto* system_profile) {
399 const RecordAllocator records(&memory_allocator);
403 std::map<uint32_t, uint32_t> field_trials;
404 bool updated = false;
406 // This is done separate from the code that gets the profile because it
407 // compartmentalizes the code and makes it possible to reuse this section
408 // should it be needed to merge "update" records into a new "complete"
409 // system profile that somehow didn't get all the updates.
410 while (records.Read(&type, &record)) {
413 // These should never be returned.
417 case kSystemProfileProto:
418 // Profile was passed in; ignore this one.
421 case kFieldTrialInfo: {
422 // Get the set of known trial IDs so duplicates don't get added.
423 if (field_trials.empty()) {
424 for (int i = 0; i < system_profile->field_trial_size(); ++i) {
425 field_trials[system_profile->field_trial(i).name_id()] =
426 system_profile->field_trial(i).group_id();
430 base::Pickle pickler(record.data(), record.size());
431 base::PickleIterator iter(pickler);
432 base::StringPiece trial;
433 base::StringPiece group;
434 if (iter.ReadStringPiece(&trial) && iter.ReadStringPiece(&group)) {
435 variations::ActiveGroupId field_ids =
436 variations::MakeActiveGroupId(trial, group);
437 if (group == kFieldTrialDeletionSentinel) {
438 field_trials.erase(field_ids.name);
440 field_trials[field_ids.name] = field_ids.group;
448 // Skip rewriting the field trials if there was no update.
453 // Rewrite the full list of field trials to avoid duplicates.
454 system_profile->clear_field_trial();
456 for (const auto& trial : field_trials) {
457 SystemProfileProto::FieldTrial* field_trial =
458 system_profile->add_field_trial();
459 field_trial->set_name_id(trial.first);
460 field_trial->set_group_id(trial.second);
464 void PersistentSystemProfile::WriteToAll(RecordType type,
465 base::StringPiece record) {
466 for (auto& allocator : allocators_)
467 allocator.Write(type, record);
470 GlobalPersistentSystemProfile* GlobalPersistentSystemProfile::GetInstance() {
471 return base::Singleton<
472 GlobalPersistentSystemProfile,
473 base::LeakySingletonTraits<GlobalPersistentSystemProfile>>::get();
476 } // namespace metrics