1 // Copyright 2012 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.
4
5 #include "base/metrics/sample_vector.h"
6
7 #include <ostream>
8 #include <string_view>
9
10 #include "base/check_op.h"
11 #include "base/compiler_specific.h"
12 #include "base/containers/heap_array.h"
13 #include "base/debug/crash_logging.h"
14 #include "base/debug/leak_annotations.h"
15 #include "base/lazy_instance.h"
16 #include "base/memory/ptr_util.h"
17 #include "base/memory/raw_span.h"
18 #include "base/metrics/histogram_macros.h"
19 #include "base/metrics/persistent_memory_allocator.h"
20 #include "base/notreached.h"
21 #include "base/numerics/safe_conversions.h"
22 #include "base/strings/strcat.h"
23 #include "base/strings/string_number_conversions.h"
24 #include "base/strings/stringprintf.h"
25 #include "base/synchronization/lock.h"
26 #include "base/threading/platform_thread.h"
27
28 // This SampleVector makes use of the single-sample embedded in the base
29 // HistogramSamples class. If the count is non-zero then there is guaranteed
30 // (within the bounds of "eventual consistency") to be no allocated external
31 // storage. Once the full counts storage is allocated, the single-sample must
32 // be extracted and disabled.
33
34 namespace base {
35
36 typedef HistogramBase::Count Count;
37 typedef HistogramBase::Sample Sample;
38
39 namespace {
40
41 // An iterator for sample vectors.
42 template <typename T>
43 class IteratorTemplate : public SampleCountIterator {
44 public:
IteratorTemplate(base::span<T> counts,const BucketRanges * bucket_ranges)45 IteratorTemplate(base::span<T> counts, const BucketRanges* bucket_ranges)
46 : counts_(counts), bucket_ranges_(bucket_ranges) {
47 SkipEmptyBuckets();
48 }
49
50 ~IteratorTemplate() override;
51
52 // SampleCountIterator:
Done() const53 bool Done() const override { return index_ >= counts_.size(); }
Next()54 void Next() override {
55 DCHECK(!Done());
56 index_++;
57 SkipEmptyBuckets();
58 }
59 void Get(HistogramBase::Sample* min,
60 int64_t* max,
61 HistogramBase::Count* count) override;
62
63 // SampleVector uses predefined buckets, so iterator can return bucket index.
GetBucketIndex(size_t * index) const64 bool GetBucketIndex(size_t* index) const override {
65 DCHECK(!Done());
66 if (index != nullptr) {
67 *index = index_;
68 }
69 return true;
70 }
71
72 private:
SkipEmptyBuckets()73 void SkipEmptyBuckets() {
74 if (Done()) {
75 return;
76 }
77
78 while (index_ < counts_.size()) {
79 if (subtle::NoBarrier_Load(&counts_[index_]) != 0) {
80 return;
81 }
82 index_++;
83 }
84 }
85
86 raw_span<T> counts_;
87 raw_ptr<const BucketRanges> bucket_ranges_;
88 size_t index_ = 0;
89 };
90
91 using SampleVectorIterator = IteratorTemplate<const HistogramBase::AtomicCount>;
92
93 template <>
94 SampleVectorIterator::~IteratorTemplate() = default;
95
96 // Get() for an iterator of a SampleVector.
97 template <>
Get(HistogramBase::Sample * min,int64_t * max,HistogramBase::Count * count)98 void SampleVectorIterator::Get(HistogramBase::Sample* min,
99 int64_t* max,
100 HistogramBase::Count* count) {
101 DCHECK(!Done());
102 *min = bucket_ranges_->range(index_);
103 *max = strict_cast<int64_t>(bucket_ranges_->range(index_ + 1));
104 *count = subtle::NoBarrier_Load(&counts_[index_]);
105 }
106
107 using ExtractingSampleVectorIterator =
108 IteratorTemplate<HistogramBase::AtomicCount>;
109
110 template <>
~IteratorTemplate()111 ExtractingSampleVectorIterator::~IteratorTemplate() {
112 // Ensure that the user has consumed all the samples in order to ensure no
113 // samples are lost.
114 DCHECK(Done());
115 }
116
117 // Get() for an extracting iterator of a SampleVector.
118 template <>
Get(HistogramBase::Sample * min,int64_t * max,HistogramBase::Count * count)119 void ExtractingSampleVectorIterator::Get(HistogramBase::Sample* min,
120 int64_t* max,
121 HistogramBase::Count* count) {
122 DCHECK(!Done());
123 *min = bucket_ranges_->range(index_);
124 *max = strict_cast<int64_t>(bucket_ranges_->range(index_ + 1));
125 *count = subtle::NoBarrier_AtomicExchange(&counts_[index_], 0);
126 }
127
128 } // namespace
129
SampleVectorBase(uint64_t id,Metadata * meta,const BucketRanges * bucket_ranges)130 SampleVectorBase::SampleVectorBase(uint64_t id,
131 Metadata* meta,
132 const BucketRanges* bucket_ranges)
133 : HistogramSamples(id, meta),
134 bucket_ranges_(bucket_ranges),
135 counts_size_(bucket_ranges_->bucket_count()) {
136 CHECK_GE(counts_size_, 1u);
137 }
138
SampleVectorBase(uint64_t id,std::unique_ptr<Metadata> meta,const BucketRanges * bucket_ranges)139 SampleVectorBase::SampleVectorBase(uint64_t id,
140 std::unique_ptr<Metadata> meta,
141 const BucketRanges* bucket_ranges)
142 : HistogramSamples(id, std::move(meta)),
143 bucket_ranges_(bucket_ranges),
144 counts_size_(bucket_ranges_->bucket_count()) {
145 CHECK_GE(counts_size_, 1u);
146 }
147
148 SampleVectorBase::~SampleVectorBase() = default;
149
Accumulate(Sample value,Count count)150 void SampleVectorBase::Accumulate(Sample value, Count count) {
151 const size_t bucket_index = GetBucketIndex(value);
152
153 // Handle the single-sample case.
154 if (!counts().has_value()) {
155 // Try to accumulate the parameters into the single-count entry.
156 if (AccumulateSingleSample(value, count, bucket_index)) {
157 // A race condition could lead to a new single-sample being accumulated
158 // above just after another thread executed the MountCountsStorage below.
159 // Since it is mounted, it could be mounted elsewhere and have values
160 // written to it. It's not allowed to have both a single-sample and
161 // entries in the counts array so move the single-sample.
162 if (counts().has_value()) {
163 MoveSingleSampleToCounts();
164 }
165 return;
166 }
167
168 // Need real storage to store both what was in the single-sample plus the
169 // parameter information.
170 MountCountsStorageAndMoveSingleSample();
171 }
172
173 // Handle the multi-sample case.
174 Count new_bucket_count =
175 subtle::NoBarrier_AtomicIncrement(&counts_at(bucket_index), count);
176 IncreaseSumAndCount(strict_cast<int64_t>(count) * value, count);
177
178 // TODO(bcwhite) Remove after crbug.com/682680.
179 Count old_bucket_count = new_bucket_count - count;
180 bool record_negative_sample =
181 (new_bucket_count >= 0) != (old_bucket_count >= 0) && count > 0;
182 if (record_negative_sample) [[unlikely]] {
183 RecordNegativeSample(SAMPLES_ACCUMULATE_OVERFLOW, count);
184 }
185 }
186
GetCount(Sample value) const187 Count SampleVectorBase::GetCount(Sample value) const {
188 return GetCountAtIndex(GetBucketIndex(value));
189 }
190
TotalCount() const191 Count SampleVectorBase::TotalCount() const {
192 // Handle the single-sample case.
193 SingleSample sample = single_sample().Load();
194 if (sample.count != 0) {
195 return sample.count;
196 }
197
198 // Handle the multi-sample case.
199 if (counts().has_value() || MountExistingCountsStorage()) {
200 Count count = 0;
201 // TODO(danakj): In C++23 we can skip the `counts_span` lvalue and iterate
202 // over `counts().value()` directly without creating a dangling reference.
203 span<const HistogramBase::AtomicCount> counts_span = counts().value();
204 for (const HistogramBase::AtomicCount& c : counts_span) {
205 count += subtle::NoBarrier_Load(&c);
206 }
207 return count;
208 }
209
210 // And the no-value case.
211 return 0;
212 }
213
GetCountAtIndex(size_t bucket_index) const214 Count SampleVectorBase::GetCountAtIndex(size_t bucket_index) const {
215 DCHECK(bucket_index < counts_size());
216
217 // Handle the single-sample case.
218 SingleSample sample = single_sample().Load();
219 if (sample.count != 0) {
220 return sample.bucket == bucket_index ? sample.count : 0;
221 }
222
223 // Handle the multi-sample case.
224 if (counts().has_value() || MountExistingCountsStorage()) {
225 return subtle::NoBarrier_Load(&counts_at(bucket_index));
226 }
227
228 // And the no-value case.
229 return 0;
230 }
231
Iterator() const232 std::unique_ptr<SampleCountIterator> SampleVectorBase::Iterator() const {
233 // Handle the single-sample case.
234 SingleSample sample = single_sample().Load();
235 if (sample.count != 0) {
236 static_assert(std::is_unsigned<decltype(SingleSample::bucket)>::value);
237 if (sample.bucket >= bucket_ranges_->bucket_count()) {
238 // Return an empty iterator if the specified bucket is invalid (e.g. due
239 // to corruption). If a different sample is eventually emitted, we will
240 // move from SingleSample to a counts storage, and that time, we will
241 // discard this invalid sample (see MoveSingleSampleToCounts()).
242 return std::make_unique<SampleVectorIterator>(
243 base::span<const HistogramBase::AtomicCount>(), bucket_ranges_);
244 }
245
246 return std::make_unique<SingleSampleIterator>(
247 bucket_ranges_->range(sample.bucket),
248 bucket_ranges_->range(sample.bucket + 1), sample.count, sample.bucket,
249 /*value_was_extracted=*/false);
250 }
251
252 // Handle the multi-sample case.
253 if (counts().has_value() || MountExistingCountsStorage()) {
254 return std::make_unique<SampleVectorIterator>(*counts(), bucket_ranges_);
255 }
256
257 // And the no-value case.
258 return std::make_unique<SampleVectorIterator>(
259 base::span<const HistogramBase::AtomicCount>(), bucket_ranges_);
260 }
261
ExtractingIterator()262 std::unique_ptr<SampleCountIterator> SampleVectorBase::ExtractingIterator() {
263 // Handle the single-sample case.
264 SingleSample sample = single_sample().Extract();
265 if (sample.count != 0) {
266 static_assert(std::is_unsigned<decltype(SingleSample::bucket)>::value);
267 if (sample.bucket >= bucket_ranges_->bucket_count()) {
268 // Return an empty iterator if the specified bucket is invalid (e.g. due
269 // to corruption). Note that we've already removed the sample from the
270 // underlying data, so this invalid sample is discarded.
271 return std::make_unique<ExtractingSampleVectorIterator>(
272 base::span<HistogramBase::AtomicCount>(), bucket_ranges_);
273 }
274
275 // Note that we have already extracted the samples (i.e., reset the
276 // underlying data back to 0 samples), even before the iterator has been
277 // used. This means that the caller needs to ensure that this value is
278 // eventually consumed, otherwise the sample is lost. There is no iterator
279 // that simply points to the underlying SingleSample and extracts its value
280 // on-demand because there are tricky edge cases when the SingleSample is
281 // disabled between the creation of the iterator and the actual call to
282 // Get() (for example, due to histogram changing to use a vector to store
283 // its samples).
284 return std::make_unique<SingleSampleIterator>(
285 bucket_ranges_->range(sample.bucket),
286 bucket_ranges_->range(sample.bucket + 1), sample.count, sample.bucket,
287 /*value_was_extracted=*/true);
288 }
289
290 // Handle the multi-sample case.
291 if (counts().has_value() || MountExistingCountsStorage()) {
292 return std::make_unique<ExtractingSampleVectorIterator>(*counts(),
293 bucket_ranges_);
294 }
295
296 // And the no-value case.
297 return std::make_unique<ExtractingSampleVectorIterator>(
298 base::span<HistogramBase::AtomicCount>(), bucket_ranges_);
299 }
300
AddSubtractImpl(SampleCountIterator * iter,HistogramSamples::Operator op)301 bool SampleVectorBase::AddSubtractImpl(SampleCountIterator* iter,
302 HistogramSamples::Operator op) {
303 // Stop now if there's nothing to do.
304 if (iter->Done()) {
305 return true;
306 }
307
308 HistogramBase::Count count;
309 size_t dest_index = GetDestinationBucketIndexAndCount(*iter, &count);
310 if (dest_index == SIZE_MAX) {
311 return false;
312 }
313
314 // Post-increment. Information about the current sample is not available
315 // after this point.
316 iter->Next();
317
318 // Single-value storage is possible if there is no counts storage and the
319 // retrieved entry is the only one in the iterator.
320 if (!counts().has_value()) {
321 if (iter->Done()) {
322 // Don't call AccumulateSingleSample because that updates sum and count
323 // which was already done by the caller of this method.
324 if (single_sample().Accumulate(
325 dest_index, op == HistogramSamples::ADD ? count : -count)) {
326 // Handle race-condition that mounted counts storage between above and
327 // here.
328 if (counts().has_value()) {
329 MoveSingleSampleToCounts();
330 }
331 return true;
332 }
333 }
334
335 // The counts storage will be needed to hold the multiple incoming values.
336 MountCountsStorageAndMoveSingleSample();
337 }
338
339 // Go through the iterator and add the counts into correct bucket.
340 while (true) {
341 // Sample's bucket matches exactly. Adjust count.
342 subtle::NoBarrier_AtomicIncrement(
343 &counts_at(dest_index), op == HistogramSamples::ADD ? count : -count);
344 if (iter->Done()) {
345 return true;
346 }
347
348 dest_index = GetDestinationBucketIndexAndCount(*iter, &count);
349 if (dest_index == SIZE_MAX) {
350 return false;
351 }
352 iter->Next();
353 }
354 }
355
GetDestinationBucketIndexAndCount(SampleCountIterator & iter,HistogramBase::Count * count)356 size_t SampleVectorBase::GetDestinationBucketIndexAndCount(
357 SampleCountIterator& iter,
358 HistogramBase::Count* count) {
359 HistogramBase::Sample min;
360 int64_t max;
361
362 iter.Get(&min, &max, count);
363 // If the iter has the bucket index, get there in O(1), otherwise look it up
364 // from the destination via O(logn) binary search.
365 size_t bucket_index;
366 if (!iter.GetBucketIndex(&bucket_index)) {
367 bucket_index = GetBucketIndex(min);
368 }
369
370 // We expect buckets to match between source and destination. If they don't,
371 // we may be trying to merge a different version of a histogram (e.g. two
372 // .pma files from different versions of the code), which is not supported.
373 // We drop the data from the iter in that case.
374 // Technically, this codepath could result in data being partially merged -
375 // i.e. if the buckets at the beginning of iter match, but later ones don't.
376 // As we expect this to be very rare, we intentionally don't handle it to
377 // avoid having to do two iterations through the buckets in AddSubtractImpl().
378 if (bucket_index >= counts_size() ||
379 min != bucket_ranges_->range(bucket_index) ||
380 max != bucket_ranges_->range(bucket_index + 1)) {
381 return SIZE_MAX;
382 }
383 return bucket_index;
384 }
385
386 // Uses simple binary search or calculates the index directly if it's an "exact"
387 // linear histogram. This is very general, but there are better approaches if we
388 // knew that the buckets were linearly distributed.
GetBucketIndex(Sample value) const389 size_t SampleVectorBase::GetBucketIndex(Sample value) const {
390 size_t bucket_count = bucket_ranges_->bucket_count();
391 CHECK_GE(value, bucket_ranges_->range(0));
392 CHECK_LT(value, bucket_ranges_->range(bucket_count));
393
394 // For "exact" linear histograms, e.g. bucket_count = maximum + 1, their
395 // minimum is 1 and bucket sizes are 1. Thus, we don't need to binary search
396 // the bucket index. The bucket index for bucket |value| is just the |value|.
397 Sample maximum = bucket_ranges_->range(bucket_count - 1);
398 if (maximum == static_cast<Sample>(bucket_count - 1)) {
399 // |value| is in the underflow bucket.
400 if (value < 1) {
401 return 0;
402 }
403 // |value| is in the overflow bucket.
404 if (value > maximum) {
405 return bucket_count - 1;
406 }
407 return static_cast<size_t>(value);
408 }
409
410 size_t under = 0;
411 size_t over = bucket_count;
412 size_t mid;
413 do {
414 DCHECK_GE(over, under);
415 mid = under + (over - under) / 2;
416 if (mid == under) {
417 break;
418 }
419 if (bucket_ranges_->range(mid) <= value) {
420 under = mid;
421 } else {
422 over = mid;
423 }
424 } while (true);
425
426 DCHECK_LE(bucket_ranges_->range(mid), value);
427 CHECK_GT(bucket_ranges_->range(mid + 1), value);
428 return mid;
429 }
430
MoveSingleSampleToCounts()431 void SampleVectorBase::MoveSingleSampleToCounts() {
432 DCHECK(counts().has_value());
433
434 // Disable the single-sample since there is now counts storage for the data.
435 SingleSample sample = single_sample().ExtractAndDisable();
436
437 // Stop here if there is no "count" as trying to find the bucket index of
438 // an invalid (including zero) "value" will crash.
439 if (sample.count == 0) {
440 return;
441 }
442
443 // Stop here if the sample bucket would be out of range for the AtomicCount
444 // array.
445 if (sample.bucket >= counts_size()) {
446 return;
447 }
448
449 // Move the value into storage. Sum and redundant-count already account
450 // for this entry so no need to call IncreaseSumAndCount().
451 subtle::NoBarrier_AtomicIncrement(&counts_at(sample.bucket), sample.count);
452 }
453
MountCountsStorageAndMoveSingleSample()454 void SampleVectorBase::MountCountsStorageAndMoveSingleSample() {
455 // There are many SampleVector objects and the lock is needed very
456 // infrequently (just when advancing from single-sample to multi-sample) so
457 // define a single, global lock that all can use. This lock only prevents
458 // concurrent entry into the code below; access and updates to |counts_data_|
459 // still requires atomic operations.
460 static LazyInstance<Lock>::Leaky counts_lock = LAZY_INSTANCE_INITIALIZER;
461 if (counts_data_.load(std::memory_order_relaxed) == nullptr) {
462 AutoLock lock(counts_lock.Get());
463 if (counts_data_.load(std::memory_order_relaxed) == nullptr) {
464 // Create the actual counts storage while the above lock is acquired.
465 span<HistogramBase::Count> counts = CreateCountsStorageWhileLocked();
466 // Point |counts()| to the newly created storage. This is done while
467 // locked to prevent possible concurrent calls to CreateCountsStorage
468 // but, between that call and here, other threads could notice the
469 // existence of the storage and race with this to set_counts(). That's
470 // okay because (a) it's atomic and (b) it always writes the same value.
471 set_counts(counts);
472 }
473 }
474
475 // Move any single-sample into the newly mounted storage.
476 MoveSingleSampleToCounts();
477 }
478
SampleVector(const BucketRanges * bucket_ranges)479 SampleVector::SampleVector(const BucketRanges* bucket_ranges)
480 : SampleVector(0, bucket_ranges) {}
481
SampleVector(uint64_t id,const BucketRanges * bucket_ranges)482 SampleVector::SampleVector(uint64_t id, const BucketRanges* bucket_ranges)
483 : SampleVectorBase(id, std::make_unique<LocalMetadata>(), bucket_ranges) {}
484
485 SampleVector::~SampleVector() = default;
486
IsDefinitelyEmpty() const487 bool SampleVector::IsDefinitelyEmpty() const {
488 // If we are still using SingleSample, and it has a count of 0, then |this|
489 // has no samples. If we are not using SingleSample, always return false, even
490 // though it is possible that |this| has no samples (e.g. we are using a
491 // counts array and all the bucket counts are 0). If we are wrong, this will
492 // just make the caller perform some extra work thinking that |this| is
493 // non-empty.
494 AtomicSingleSample sample = single_sample();
495 return HistogramSamples::IsDefinitelyEmpty() && !sample.IsDisabled() &&
496 sample.Load().count == 0;
497 }
498
MountExistingCountsStorage() const499 bool SampleVector::MountExistingCountsStorage() const {
500 // There is never any existing storage other than what is already in use.
501 return counts().has_value();
502 }
503
GetAsciiHeader(std::string_view histogram_name,int32_t flags) const504 std::string SampleVector::GetAsciiHeader(std::string_view histogram_name,
505 int32_t flags) const {
506 Count sample_count = TotalCount();
507 std::string output;
508 StrAppend(&output, {"Histogram: ", histogram_name, " recorded ",
509 NumberToString(sample_count), " samples"});
510 if (sample_count == 0) {
511 DCHECK_EQ(sum(), 0);
512 } else {
513 double mean = static_cast<float>(sum()) / sample_count;
514 StringAppendF(&output, ", mean = %.1f", mean);
515 }
516 if (flags) {
517 StringAppendF(&output, " (flags = 0x%x)", flags);
518 }
519 return output;
520 }
521
GetAsciiBody() const522 std::string SampleVector::GetAsciiBody() const {
523 Count sample_count = TotalCount();
524
525 // Prepare to normalize graphical rendering of bucket contents.
526 double max_size = 0;
527 double scaling_factor = 1;
528 max_size = GetPeakBucketSize();
529 // Scale histogram bucket counts to take at most 72 characters.
530 // Note: Keep in sync w/ kLineLength histogram_samples.cc
531 const double kLineLength = 72;
532 if (max_size > kLineLength) {
533 scaling_factor = kLineLength / max_size;
534 }
535
536 // Calculate largest print width needed for any of our bucket range displays.
537 size_t print_width = 1;
538 for (uint32_t i = 0; i < bucket_count(); ++i) {
539 if (GetCountAtIndex(i)) {
540 size_t width =
541 GetSimpleAsciiBucketRange(bucket_ranges()->range(i)).size() + 1;
542 if (width > print_width) {
543 print_width = width;
544 }
545 }
546 }
547
548 int64_t remaining = sample_count;
549 int64_t past = 0;
550 std::string output;
551 // Output the actual histogram graph.
552 for (uint32_t i = 0; i < bucket_count(); ++i) {
553 Count current = GetCountAtIndex(i);
554 remaining -= current;
555 std::string range = GetSimpleAsciiBucketRange(bucket_ranges()->range(i));
556 output.append(range);
557 for (size_t j = 0; range.size() + j < print_width + 1; ++j) {
558 output.push_back(' ');
559 }
560 if (0 == current && i < bucket_count() - 1 && 0 == GetCountAtIndex(i + 1)) {
561 while (i < bucket_count() - 1 && 0 == GetCountAtIndex(i + 1)) {
562 ++i;
563 }
564 output.append("... \n");
565 continue; // No reason to plot emptiness.
566 }
567 Count current_size = round(current * scaling_factor);
568 WriteAsciiBucketGraph(current_size, kLineLength, &output);
569 WriteAsciiBucketContext(past, current, remaining, i, &output);
570 output.append("\n");
571 past += current;
572 }
573 DCHECK_EQ(sample_count, past);
574 return output;
575 }
576
GetPeakBucketSize() const577 double SampleVector::GetPeakBucketSize() const {
578 Count max = 0;
579 for (uint32_t i = 0; i < bucket_count(); ++i) {
580 Count current = GetCountAtIndex(i);
581 if (current > max) {
582 max = current;
583 }
584 }
585 return max;
586 }
587
WriteAsciiBucketContext(int64_t past,Count current,int64_t remaining,uint32_t current_bucket_index,std::string * output) const588 void SampleVector::WriteAsciiBucketContext(int64_t past,
589 Count current,
590 int64_t remaining,
591 uint32_t current_bucket_index,
592 std::string* output) const {
593 double scaled_sum = (past + current + remaining) / 100.0;
594 WriteAsciiBucketValue(current, scaled_sum, output);
595 if (0 < current_bucket_index) {
596 double percentage = past / scaled_sum;
597 StringAppendF(output, " {%3.1f%%}", percentage);
598 }
599 }
600
601 span<HistogramBase::AtomicCount>
CreateCountsStorageWhileLocked()602 SampleVector::CreateCountsStorageWhileLocked() {
603 local_counts_.resize(counts_size());
604 return local_counts_;
605 }
606
PersistentSampleVector(uint64_t id,const BucketRanges * bucket_ranges,Metadata * meta,const DelayedPersistentAllocation & counts)607 PersistentSampleVector::PersistentSampleVector(
608 uint64_t id,
609 const BucketRanges* bucket_ranges,
610 Metadata* meta,
611 const DelayedPersistentAllocation& counts)
612 : SampleVectorBase(id, meta, bucket_ranges), persistent_counts_(counts) {
613 // Only mount the full storage if the single-sample has been disabled.
614 // Otherwise, it is possible for this object instance to start using (empty)
615 // storage that was created incidentally while another instance continues to
616 // update to the single sample. This "incidental creation" can happen because
617 // the memory is a DelayedPersistentAllocation which allows multiple memory
618 // blocks within it and applies an all-or-nothing approach to the allocation.
619 // Thus, a request elsewhere for one of the _other_ blocks would make _this_
620 // block available even though nothing has explicitly requested it.
621 //
622 // Note that it's not possible for the ctor to mount existing storage and
623 // move any single-sample to it because sometimes the persistent memory is
624 // read-only. Only non-const methods (which assume that memory is read/write)
625 // can do that.
626 if (single_sample().IsDisabled()) {
627 bool success = MountExistingCountsStorage();
628 DCHECK(success);
629 }
630 }
631
632 PersistentSampleVector::~PersistentSampleVector() = default;
633
IsDefinitelyEmpty() const634 bool PersistentSampleVector::IsDefinitelyEmpty() const {
635 // Not implemented.
636 NOTREACHED();
637 }
638
MountExistingCountsStorage() const639 bool PersistentSampleVector::MountExistingCountsStorage() const {
640 // There is no early exit if counts is not yet mounted because, given that
641 // this is a virtual function, it's more efficient to do that at the call-
642 // site. There is no danger, however, should this get called anyway (perhaps
643 // because of a race condition) because at worst the `counts_data_` and
644 // `counts_size_` members would be over-written (in an atomic manner)
645 // with the exact same values.
646
647 if (!persistent_counts_.reference()) {
648 return false; // Nothing to mount.
649 }
650
651 // Mount the counts array in position. This shouldn't fail but can if the
652 // data is corrupt or incomplete.
653 span<HistogramBase::AtomicCount> mem =
654 persistent_counts_.Get<HistogramBase::AtomicCount>();
655 if (mem.empty()) {
656 return false;
657 }
658 // Uses a span that only covers the counts the SampleVector should have
659 // access to, which can be a subset of the entire persistent allocation.
660 set_counts(mem.first(counts_size()));
661 return true;
662 }
663
664 span<HistogramBase::AtomicCount>
CreateCountsStorageWhileLocked()665 PersistentSampleVector::CreateCountsStorageWhileLocked() {
666 span<HistogramBase::AtomicCount> mem =
667 persistent_counts_.Get<HistogramBase::AtomicCount>();
668 if (mem.empty()) {
669 // The above shouldn't fail but can if Bad Things(tm) are occurring in
670 // the persistent allocator. Crashing isn't a good option so instead
671 // just allocate something from the heap that we will leak and return that.
672 // There will be no sharing or persistence but worse things are already
673 // happening.
674 auto array = HeapArray<HistogramBase::AtomicCount>::WithSize(counts_size());
675 ANNOTATE_LEAKING_OBJECT_PTR(array.data());
676 return std::move(array).leak();
677 }
678
679 // Returns a span that only covers the counts the SampleVector should have
680 // access to, which can be a subset of the entire persistent allocation.
681 return mem.first(counts_size());
682 }
683
684 } // namespace base
685