|  | // Copyright (c) 2012 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 "base/metrics/sample_vector.h" | 
|  |  | 
|  | #include "base/lazy_instance.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/memory/ptr_util.h" | 
|  | #include "base/metrics/persistent_memory_allocator.h" | 
|  | #include "base/numerics/safe_conversions.h" | 
|  | #include "base/synchronization/lock.h" | 
|  | #include "base/threading/platform_thread.h" | 
|  |  | 
|  | // This SampleVector makes use of the single-sample embedded in the base | 
|  | // HistogramSamples class. If the count is non-zero then there is guaranteed | 
|  | // (within the bounds of "eventual consistency") to be no allocated external | 
|  | // storage. Once the full counts storage is allocated, the single-sample must | 
|  | // be extracted and disabled. | 
|  |  | 
|  | namespace base { | 
|  |  | 
|  | typedef HistogramBase::Count Count; | 
|  | typedef HistogramBase::Sample Sample; | 
|  |  | 
|  | SampleVectorBase::SampleVectorBase(uint64_t id, | 
|  | Metadata* meta, | 
|  | const BucketRanges* bucket_ranges) | 
|  | : HistogramSamples(id, meta), bucket_ranges_(bucket_ranges) { | 
|  | CHECK_GE(bucket_ranges_->bucket_count(), 1u); | 
|  | } | 
|  |  | 
|  | SampleVectorBase::~SampleVectorBase() = default; | 
|  |  | 
|  | void SampleVectorBase::Accumulate(Sample value, Count count) { | 
|  | const size_t bucket_index = GetBucketIndex(value); | 
|  |  | 
|  | // Handle the single-sample case. | 
|  | if (!counts()) { | 
|  | // Try to accumulate the parameters into the single-count entry. | 
|  | if (AccumulateSingleSample(value, count, bucket_index)) { | 
|  | // A race condition could lead to a new single-sample being accumulated | 
|  | // above just after another thread executed the MountCountsStorage below. | 
|  | // Since it is mounted, it could be mounted elsewhere and have values | 
|  | // written to it. It's not allowed to have both a single-sample and | 
|  | // entries in the counts array so move the single-sample. | 
|  | if (counts()) | 
|  | MoveSingleSampleToCounts(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Need real storage to store both what was in the single-sample plus the | 
|  | // parameter information. | 
|  | MountCountsStorageAndMoveSingleSample(); | 
|  | } | 
|  |  | 
|  | // Handle the multi-sample case. | 
|  | Count new_value = | 
|  | subtle::NoBarrier_AtomicIncrement(&counts()[bucket_index], count); | 
|  | IncreaseSumAndCount(strict_cast<int64_t>(count) * value, count); | 
|  |  | 
|  | // TODO(bcwhite) Remove after crbug.com/682680. | 
|  | Count old_value = new_value - count; | 
|  | if ((new_value >= 0) != (old_value >= 0) && count > 0) | 
|  | RecordNegativeSample(SAMPLES_ACCUMULATE_OVERFLOW, count); | 
|  | } | 
|  |  | 
|  | Count SampleVectorBase::GetCount(Sample value) const { | 
|  | return GetCountAtIndex(GetBucketIndex(value)); | 
|  | } | 
|  |  | 
|  | Count SampleVectorBase::TotalCount() const { | 
|  | // Handle the single-sample case. | 
|  | SingleSample sample = single_sample().Load(); | 
|  | if (sample.count != 0) | 
|  | return sample.count; | 
|  |  | 
|  | // Handle the multi-sample case. | 
|  | if (counts() || MountExistingCountsStorage()) { | 
|  | Count count = 0; | 
|  | size_t size = counts_size(); | 
|  | const HistogramBase::AtomicCount* counts_array = counts(); | 
|  | for (size_t i = 0; i < size; ++i) { | 
|  | count += subtle::NoBarrier_Load(&counts_array[i]); | 
|  | } | 
|  | return count; | 
|  | } | 
|  |  | 
|  | // And the no-value case. | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | Count SampleVectorBase::GetCountAtIndex(size_t bucket_index) const { | 
|  | DCHECK(bucket_index < counts_size()); | 
|  |  | 
|  | // Handle the single-sample case. | 
|  | SingleSample sample = single_sample().Load(); | 
|  | if (sample.count != 0) | 
|  | return sample.bucket == bucket_index ? sample.count : 0; | 
|  |  | 
|  | // Handle the multi-sample case. | 
|  | if (counts() || MountExistingCountsStorage()) | 
|  | return subtle::NoBarrier_Load(&counts()[bucket_index]); | 
|  |  | 
|  | // And the no-value case. | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<SampleCountIterator> SampleVectorBase::Iterator() const { | 
|  | // Handle the single-sample case. | 
|  | SingleSample sample = single_sample().Load(); | 
|  | if (sample.count != 0) { | 
|  | return std::make_unique<SingleSampleIterator>( | 
|  | bucket_ranges_->range(sample.bucket), | 
|  | bucket_ranges_->range(sample.bucket + 1), sample.count, sample.bucket); | 
|  | } | 
|  |  | 
|  | // Handle the multi-sample case. | 
|  | if (counts() || MountExistingCountsStorage()) { | 
|  | return std::make_unique<SampleVectorIterator>(counts(), counts_size(), | 
|  | bucket_ranges_); | 
|  | } | 
|  |  | 
|  | // And the no-value case. | 
|  | return std::make_unique<SampleVectorIterator>(nullptr, 0, bucket_ranges_); | 
|  | } | 
|  |  | 
|  | bool SampleVectorBase::AddSubtractImpl(SampleCountIterator* iter, | 
|  | HistogramSamples::Operator op) { | 
|  | // Stop now if there's nothing to do. | 
|  | if (iter->Done()) | 
|  | return true; | 
|  |  | 
|  | // Get the first value and its index. | 
|  | HistogramBase::Sample min; | 
|  | int64_t max; | 
|  | HistogramBase::Count count; | 
|  | iter->Get(&min, &max, &count); | 
|  | size_t dest_index = GetBucketIndex(min); | 
|  |  | 
|  | // The destination must be a superset of the source meaning that though the | 
|  | // incoming ranges will find an exact match, the incoming bucket-index, if | 
|  | // it exists, may be offset from the destination bucket-index. Calculate | 
|  | // that offset of the passed iterator; there are are no overflow checks | 
|  | // because 2's compliment math will work it out in the end. | 
|  | // | 
|  | // Because GetBucketIndex() always returns the same true or false result for | 
|  | // a given iterator object, |index_offset| is either set here and used below, | 
|  | // or never set and never used. The compiler doesn't know this, though, which | 
|  | // is why it's necessary to initialize it to something. | 
|  | size_t index_offset = 0; | 
|  | size_t iter_index; | 
|  | if (iter->GetBucketIndex(&iter_index)) | 
|  | index_offset = dest_index - iter_index; | 
|  | if (dest_index >= counts_size()) | 
|  | return false; | 
|  |  | 
|  | // Post-increment. Information about the current sample is not available | 
|  | // after this point. | 
|  | iter->Next(); | 
|  |  | 
|  | // Single-value storage is possible if there is no counts storage and the | 
|  | // retrieved entry is the only one in the iterator. | 
|  | if (!counts()) { | 
|  | if (iter->Done()) { | 
|  | // Don't call AccumulateSingleSample because that updates sum and count | 
|  | // which was already done by the caller of this method. | 
|  | if (single_sample().Accumulate( | 
|  | dest_index, op == HistogramSamples::ADD ? count : -count)) { | 
|  | // Handle race-condition that mounted counts storage between above and | 
|  | // here. | 
|  | if (counts()) | 
|  | MoveSingleSampleToCounts(); | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | // The counts storage will be needed to hold the multiple incoming values. | 
|  | MountCountsStorageAndMoveSingleSample(); | 
|  | } | 
|  |  | 
|  | // Go through the iterator and add the counts into correct bucket. | 
|  | while (true) { | 
|  | // Ensure that the sample's min/max match the ranges min/max. | 
|  | if (min != bucket_ranges_->range(dest_index) || | 
|  | max != bucket_ranges_->range(dest_index + 1)) { | 
|  | NOTREACHED() << "sample=" << min << "," << max | 
|  | << "; range=" << bucket_ranges_->range(dest_index) << "," | 
|  | << bucket_ranges_->range(dest_index + 1); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Sample's bucket matches exactly. Adjust count. | 
|  | subtle::NoBarrier_AtomicIncrement( | 
|  | &counts()[dest_index], op == HistogramSamples::ADD ? count : -count); | 
|  |  | 
|  | // Advance to the next iterable sample. See comments above for how | 
|  | // everything works. | 
|  | if (iter->Done()) | 
|  | return true; | 
|  | iter->Get(&min, &max, &count); | 
|  | if (iter->GetBucketIndex(&iter_index)) { | 
|  | // Destination bucket is a known offset from the source bucket. | 
|  | dest_index = iter_index + index_offset; | 
|  | } else { | 
|  | // Destination bucket has to be determined anew each time. | 
|  | dest_index = GetBucketIndex(min); | 
|  | } | 
|  | if (dest_index >= counts_size()) | 
|  | return false; | 
|  | iter->Next(); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Use simple binary search.  This is very general, but there are better | 
|  | // approaches if we knew that the buckets were linearly distributed. | 
|  | size_t SampleVectorBase::GetBucketIndex(Sample value) const { | 
|  | size_t bucket_count = bucket_ranges_->bucket_count(); | 
|  | CHECK_GE(bucket_count, 1u); | 
|  | CHECK_GE(value, bucket_ranges_->range(0)); | 
|  | CHECK_LT(value, bucket_ranges_->range(bucket_count)); | 
|  |  | 
|  | size_t under = 0; | 
|  | size_t over = bucket_count; | 
|  | size_t mid; | 
|  | do { | 
|  | DCHECK_GE(over, under); | 
|  | mid = under + (over - under)/2; | 
|  | if (mid == under) | 
|  | break; | 
|  | if (bucket_ranges_->range(mid) <= value) | 
|  | under = mid; | 
|  | else | 
|  | over = mid; | 
|  | } while (true); | 
|  |  | 
|  | DCHECK_LE(bucket_ranges_->range(mid), value); | 
|  | CHECK_GT(bucket_ranges_->range(mid + 1), value); | 
|  | return mid; | 
|  | } | 
|  |  | 
|  | void SampleVectorBase::MoveSingleSampleToCounts() { | 
|  | DCHECK(counts()); | 
|  |  | 
|  | // Disable the single-sample since there is now counts storage for the data. | 
|  | SingleSample sample = single_sample().Extract(/*disable=*/true); | 
|  |  | 
|  | // Stop here if there is no "count" as trying to find the bucket index of | 
|  | // an invalid (including zero) "value" will crash. | 
|  | if (sample.count == 0) | 
|  | return; | 
|  |  | 
|  | // Move the value into storage. Sum and redundant-count already account | 
|  | // for this entry so no need to call IncreaseSumAndCount(). | 
|  | subtle::NoBarrier_AtomicIncrement(&counts()[sample.bucket], sample.count); | 
|  | } | 
|  |  | 
|  | void SampleVectorBase::MountCountsStorageAndMoveSingleSample() { | 
|  | // There are many SampleVector objects and the lock is needed very | 
|  | // infrequently (just when advancing from single-sample to multi-sample) so | 
|  | // define a single, global lock that all can use. This lock only prevents | 
|  | // concurrent entry into the code below; access and updates to |counts_| | 
|  | // still requires atomic operations. | 
|  | static LazyInstance<Lock>::Leaky counts_lock = LAZY_INSTANCE_INITIALIZER; | 
|  | if (subtle::NoBarrier_Load(&counts_) == 0) { | 
|  | AutoLock lock(counts_lock.Get()); | 
|  | if (subtle::NoBarrier_Load(&counts_) == 0) { | 
|  | // Create the actual counts storage while the above lock is acquired. | 
|  | HistogramBase::Count* counts = CreateCountsStorageWhileLocked(); | 
|  | DCHECK(counts); | 
|  |  | 
|  | // Point |counts_| to the newly created storage. This is done while | 
|  | // locked to prevent possible concurrent calls to CreateCountsStorage | 
|  | // but, between that call and here, other threads could notice the | 
|  | // existence of the storage and race with this to set_counts(). That's | 
|  | // okay because (a) it's atomic and (b) it always writes the same value. | 
|  | set_counts(counts); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Move any single-sample into the newly mounted storage. | 
|  | MoveSingleSampleToCounts(); | 
|  | } | 
|  |  | 
|  | SampleVector::SampleVector(const BucketRanges* bucket_ranges) | 
|  | : SampleVector(0, bucket_ranges) {} | 
|  |  | 
|  | SampleVector::SampleVector(uint64_t id, const BucketRanges* bucket_ranges) | 
|  | : SampleVectorBase(id, new LocalMetadata(), bucket_ranges) {} | 
|  |  | 
|  | SampleVector::~SampleVector() { | 
|  | delete static_cast<LocalMetadata*>(meta()); | 
|  | } | 
|  |  | 
|  | bool SampleVector::MountExistingCountsStorage() const { | 
|  | // There is never any existing storage other than what is already in use. | 
|  | return counts() != nullptr; | 
|  | } | 
|  |  | 
|  | HistogramBase::AtomicCount* SampleVector::CreateCountsStorageWhileLocked() { | 
|  | local_counts_.resize(counts_size()); | 
|  | return &local_counts_[0]; | 
|  | } | 
|  |  | 
|  | PersistentSampleVector::PersistentSampleVector( | 
|  | uint64_t id, | 
|  | const BucketRanges* bucket_ranges, | 
|  | Metadata* meta, | 
|  | const DelayedPersistentAllocation& counts) | 
|  | : SampleVectorBase(id, meta, bucket_ranges), persistent_counts_(counts) { | 
|  | // Only mount the full storage if the single-sample has been disabled. | 
|  | // Otherwise, it is possible for this object instance to start using (empty) | 
|  | // storage that was created incidentally while another instance continues to | 
|  | // update to the single sample. This "incidental creation" can happen because | 
|  | // the memory is a DelayedPersistentAllocation which allows multiple memory | 
|  | // blocks within it and applies an all-or-nothing approach to the allocation. | 
|  | // Thus, a request elsewhere for one of the _other_ blocks would make _this_ | 
|  | // block available even though nothing has explicitly requested it. | 
|  | // | 
|  | // Note that it's not possible for the ctor to mount existing storage and | 
|  | // move any single-sample to it because sometimes the persistent memory is | 
|  | // read-only. Only non-const methods (which assume that memory is read/write) | 
|  | // can do that. | 
|  | if (single_sample().IsDisabled()) { | 
|  | bool success = MountExistingCountsStorage(); | 
|  | DCHECK(success); | 
|  | } | 
|  | } | 
|  |  | 
|  | PersistentSampleVector::~PersistentSampleVector() = default; | 
|  |  | 
|  | bool PersistentSampleVector::MountExistingCountsStorage() const { | 
|  | // There is no early exit if counts is not yet mounted because, given that | 
|  | // this is a virtual function, it's more efficient to do that at the call- | 
|  | // site. There is no danger, however, should this get called anyway (perhaps | 
|  | // because of a race condition) because at worst the |counts_| value would | 
|  | // be over-written (in an atomic manner) with the exact same address. | 
|  |  | 
|  | if (!persistent_counts_.reference()) | 
|  | return false;  // Nothing to mount. | 
|  |  | 
|  | // Mount the counts array in position. | 
|  | set_counts( | 
|  | static_cast<HistogramBase::AtomicCount*>(persistent_counts_.Get())); | 
|  |  | 
|  | // The above shouldn't fail but can if the data is corrupt or incomplete. | 
|  | return counts() != nullptr; | 
|  | } | 
|  |  | 
|  | HistogramBase::AtomicCount* | 
|  | PersistentSampleVector::CreateCountsStorageWhileLocked() { | 
|  | void* mem = persistent_counts_.Get(); | 
|  | if (!mem) { | 
|  | // The above shouldn't fail but can if Bad Things(tm) are occurring in the | 
|  | // persistent allocator. Crashing isn't a good option so instead just | 
|  | // allocate something from the heap and return that. There will be no | 
|  | // sharing or persistence but worse things are already happening. | 
|  | return new HistogramBase::AtomicCount[counts_size()]; | 
|  | } | 
|  |  | 
|  | return static_cast<HistogramBase::AtomicCount*>(mem); | 
|  | } | 
|  |  | 
|  | SampleVectorIterator::SampleVectorIterator( | 
|  | const std::vector<HistogramBase::AtomicCount>* counts, | 
|  | const BucketRanges* bucket_ranges) | 
|  | : counts_(&(*counts)[0]), | 
|  | counts_size_(counts->size()), | 
|  | bucket_ranges_(bucket_ranges), | 
|  | index_(0) { | 
|  | DCHECK_GE(bucket_ranges_->bucket_count(), counts_size_); | 
|  | SkipEmptyBuckets(); | 
|  | } | 
|  |  | 
|  | SampleVectorIterator::SampleVectorIterator( | 
|  | const HistogramBase::AtomicCount* counts, | 
|  | size_t counts_size, | 
|  | const BucketRanges* bucket_ranges) | 
|  | : counts_(counts), | 
|  | counts_size_(counts_size), | 
|  | bucket_ranges_(bucket_ranges), | 
|  | index_(0) { | 
|  | DCHECK_GE(bucket_ranges_->bucket_count(), counts_size_); | 
|  | SkipEmptyBuckets(); | 
|  | } | 
|  |  | 
|  | SampleVectorIterator::~SampleVectorIterator() = default; | 
|  |  | 
|  | bool SampleVectorIterator::Done() const { | 
|  | return index_ >= counts_size_; | 
|  | } | 
|  |  | 
|  | void SampleVectorIterator::Next() { | 
|  | DCHECK(!Done()); | 
|  | index_++; | 
|  | SkipEmptyBuckets(); | 
|  | } | 
|  |  | 
|  | void SampleVectorIterator::Get(HistogramBase::Sample* min, | 
|  | int64_t* max, | 
|  | HistogramBase::Count* count) const { | 
|  | DCHECK(!Done()); | 
|  | if (min != nullptr) | 
|  | *min = bucket_ranges_->range(index_); | 
|  | if (max != nullptr) | 
|  | *max = strict_cast<int64_t>(bucket_ranges_->range(index_ + 1)); | 
|  | if (count != nullptr) | 
|  | *count = subtle::NoBarrier_Load(&counts_[index_]); | 
|  | } | 
|  |  | 
|  | bool SampleVectorIterator::GetBucketIndex(size_t* index) const { | 
|  | DCHECK(!Done()); | 
|  | if (index != nullptr) | 
|  | *index = index_; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void SampleVectorIterator::SkipEmptyBuckets() { | 
|  | if (Done()) | 
|  | return; | 
|  |  | 
|  | while (index_ < counts_size_) { | 
|  | if (subtle::NoBarrier_Load(&counts_[index_]) != 0) | 
|  | return; | 
|  | index_++; | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace base |