• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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