|  | // Copyright 2016 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/persistent_sample_map.h" | 
|  |  | 
|  | #include "base/logging.h" | 
|  | #include "base/memory/ptr_util.h" | 
|  | #include "base/metrics/histogram_macros.h" | 
|  | #include "base/metrics/persistent_histogram_allocator.h" | 
|  | #include "base/numerics/safe_conversions.h" | 
|  | #include "base/stl_util.h" | 
|  |  | 
|  | namespace base { | 
|  |  | 
|  | typedef HistogramBase::Count Count; | 
|  | typedef HistogramBase::Sample Sample; | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // An iterator for going through a PersistentSampleMap. The logic here is | 
|  | // identical to that of SampleMapIterator but with different data structures. | 
|  | // Changes here likely need to be duplicated there. | 
|  | class PersistentSampleMapIterator : public SampleCountIterator { | 
|  | public: | 
|  | typedef std::map<HistogramBase::Sample, HistogramBase::Count*> | 
|  | SampleToCountMap; | 
|  |  | 
|  | explicit PersistentSampleMapIterator(const SampleToCountMap& sample_counts); | 
|  | ~PersistentSampleMapIterator() override; | 
|  |  | 
|  | // SampleCountIterator: | 
|  | bool Done() const override; | 
|  | void Next() override; | 
|  | void Get(HistogramBase::Sample* min, | 
|  | int64_t* max, | 
|  | HistogramBase::Count* count) const override; | 
|  |  | 
|  | private: | 
|  | void SkipEmptyBuckets(); | 
|  |  | 
|  | SampleToCountMap::const_iterator iter_; | 
|  | const SampleToCountMap::const_iterator end_; | 
|  | }; | 
|  |  | 
|  | PersistentSampleMapIterator::PersistentSampleMapIterator( | 
|  | const SampleToCountMap& sample_counts) | 
|  | : iter_(sample_counts.begin()), | 
|  | end_(sample_counts.end()) { | 
|  | SkipEmptyBuckets(); | 
|  | } | 
|  |  | 
|  | PersistentSampleMapIterator::~PersistentSampleMapIterator() = default; | 
|  |  | 
|  | bool PersistentSampleMapIterator::Done() const { | 
|  | return iter_ == end_; | 
|  | } | 
|  |  | 
|  | void PersistentSampleMapIterator::Next() { | 
|  | DCHECK(!Done()); | 
|  | ++iter_; | 
|  | SkipEmptyBuckets(); | 
|  | } | 
|  |  | 
|  | void PersistentSampleMapIterator::Get(Sample* min, | 
|  | int64_t* max, | 
|  | Count* count) const { | 
|  | DCHECK(!Done()); | 
|  | if (min) | 
|  | *min = iter_->first; | 
|  | if (max) | 
|  | *max = strict_cast<int64_t>(iter_->first) + 1; | 
|  | if (count) | 
|  | *count = *iter_->second; | 
|  | } | 
|  |  | 
|  | void PersistentSampleMapIterator::SkipEmptyBuckets() { | 
|  | while (!Done() && *iter_->second == 0) { | 
|  | ++iter_; | 
|  | } | 
|  | } | 
|  |  | 
|  | // This structure holds an entry for a PersistentSampleMap within a persistent | 
|  | // memory allocator. The "id" must be unique across all maps held by an | 
|  | // allocator or they will get attached to the wrong sample map. | 
|  | struct SampleRecord { | 
|  | // SHA1(SampleRecord): Increment this if structure changes! | 
|  | static constexpr uint32_t kPersistentTypeId = 0x8FE6A69F + 1; | 
|  |  | 
|  | // Expected size for 32/64-bit check. | 
|  | static constexpr size_t kExpectedInstanceSize = 16; | 
|  |  | 
|  | uint64_t id;   // Unique identifier of owner. | 
|  | Sample value;  // The value for which this record holds a count. | 
|  | Count count;   // The count associated with the above value. | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | PersistentSampleMap::PersistentSampleMap( | 
|  | uint64_t id, | 
|  | PersistentHistogramAllocator* allocator, | 
|  | Metadata* meta) | 
|  | : HistogramSamples(id, meta), allocator_(allocator) {} | 
|  |  | 
|  | PersistentSampleMap::~PersistentSampleMap() { | 
|  | if (records_) | 
|  | records_->Release(this); | 
|  | } | 
|  |  | 
|  | void PersistentSampleMap::Accumulate(Sample value, Count count) { | 
|  | #if 0  // TODO(bcwhite) Re-enable efficient version after crbug.com/682680. | 
|  | *GetOrCreateSampleCountStorage(value) += count; | 
|  | #else | 
|  | Count* local_count_ptr = GetOrCreateSampleCountStorage(value); | 
|  | if (count < 0) { | 
|  | if (*local_count_ptr < -count) | 
|  | RecordNegativeSample(SAMPLES_ACCUMULATE_WENT_NEGATIVE, -count); | 
|  | else | 
|  | RecordNegativeSample(SAMPLES_ACCUMULATE_NEGATIVE_COUNT, -count); | 
|  | *local_count_ptr += count; | 
|  | } else { | 
|  | Sample old_value = *local_count_ptr; | 
|  | Sample new_value = old_value + count; | 
|  | *local_count_ptr = new_value; | 
|  | if ((new_value >= 0) != (old_value >= 0)) | 
|  | RecordNegativeSample(SAMPLES_ACCUMULATE_OVERFLOW, count); | 
|  | } | 
|  | #endif | 
|  | IncreaseSumAndCount(strict_cast<int64_t>(count) * value, count); | 
|  | } | 
|  |  | 
|  | Count PersistentSampleMap::GetCount(Sample value) const { | 
|  | // Have to override "const" to make sure all samples have been loaded before | 
|  | // being able to know what value to return. | 
|  | Count* count_pointer = | 
|  | const_cast<PersistentSampleMap*>(this)->GetSampleCountStorage(value); | 
|  | return count_pointer ? *count_pointer : 0; | 
|  | } | 
|  |  | 
|  | Count PersistentSampleMap::TotalCount() const { | 
|  | // Have to override "const" in order to make sure all samples have been | 
|  | // loaded before trying to iterate over the map. | 
|  | const_cast<PersistentSampleMap*>(this)->ImportSamples(-1, true); | 
|  |  | 
|  | Count count = 0; | 
|  | for (const auto& entry : sample_counts_) { | 
|  | count += *entry.second; | 
|  | } | 
|  | return count; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<SampleCountIterator> PersistentSampleMap::Iterator() const { | 
|  | // Have to override "const" in order to make sure all samples have been | 
|  | // loaded before trying to iterate over the map. | 
|  | const_cast<PersistentSampleMap*>(this)->ImportSamples(-1, true); | 
|  | return WrapUnique(new PersistentSampleMapIterator(sample_counts_)); | 
|  | } | 
|  |  | 
|  | // static | 
|  | PersistentMemoryAllocator::Reference | 
|  | PersistentSampleMap::GetNextPersistentRecord( | 
|  | PersistentMemoryAllocator::Iterator& iterator, | 
|  | uint64_t* sample_map_id) { | 
|  | const SampleRecord* record = iterator.GetNextOfObject<SampleRecord>(); | 
|  | if (!record) | 
|  | return 0; | 
|  |  | 
|  | *sample_map_id = record->id; | 
|  | return iterator.GetAsReference(record); | 
|  | } | 
|  |  | 
|  | // static | 
|  | PersistentMemoryAllocator::Reference | 
|  | PersistentSampleMap::CreatePersistentRecord( | 
|  | PersistentMemoryAllocator* allocator, | 
|  | uint64_t sample_map_id, | 
|  | Sample value) { | 
|  | SampleRecord* record = allocator->New<SampleRecord>(); | 
|  | if (!record) { | 
|  | NOTREACHED() << "full=" << allocator->IsFull() | 
|  | << ", corrupt=" << allocator->IsCorrupt(); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | record->id = sample_map_id; | 
|  | record->value = value; | 
|  | record->count = 0; | 
|  |  | 
|  | PersistentMemoryAllocator::Reference ref = allocator->GetAsReference(record); | 
|  | allocator->MakeIterable(ref); | 
|  | return ref; | 
|  | } | 
|  |  | 
|  | bool PersistentSampleMap::AddSubtractImpl(SampleCountIterator* iter, | 
|  | Operator op) { | 
|  | Sample min; | 
|  | int64_t max; | 
|  | Count count; | 
|  | for (; !iter->Done(); iter->Next()) { | 
|  | iter->Get(&min, &max, &count); | 
|  | if (count == 0) | 
|  | continue; | 
|  | if (strict_cast<int64_t>(min) + 1 != max) | 
|  | return false;  // SparseHistogram only supports bucket with size 1. | 
|  | *GetOrCreateSampleCountStorage(min) += | 
|  | (op == HistogramSamples::ADD) ? count : -count; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | Count* PersistentSampleMap::GetSampleCountStorage(Sample value) { | 
|  | // If |value| is already in the map, just return that. | 
|  | auto it = sample_counts_.find(value); | 
|  | if (it != sample_counts_.end()) | 
|  | return it->second; | 
|  |  | 
|  | // Import any new samples from persistent memory looking for the value. | 
|  | return ImportSamples(value, false); | 
|  | } | 
|  |  | 
|  | Count* PersistentSampleMap::GetOrCreateSampleCountStorage(Sample value) { | 
|  | // Get any existing count storage. | 
|  | Count* count_pointer = GetSampleCountStorage(value); | 
|  | if (count_pointer) | 
|  | return count_pointer; | 
|  |  | 
|  | // Create a new record in persistent memory for the value. |records_| will | 
|  | // have been initialized by the GetSampleCountStorage() call above. | 
|  | DCHECK(records_); | 
|  | PersistentMemoryAllocator::Reference ref = records_->CreateNew(value); | 
|  | if (!ref) { | 
|  | // If a new record could not be created then the underlying allocator is | 
|  | // full or corrupt. Instead, allocate the counter from the heap. This | 
|  | // sample will not be persistent, will not be shared, and will leak... | 
|  | // but it's better than crashing. | 
|  | count_pointer = new Count(0); | 
|  | sample_counts_[value] = count_pointer; | 
|  | return count_pointer; | 
|  | } | 
|  |  | 
|  | // A race condition between two independent processes (i.e. two independent | 
|  | // histogram objects sharing the same sample data) could cause two of the | 
|  | // above records to be created. The allocator, however, forces a strict | 
|  | // ordering on iterable objects so use the import method to actually add the | 
|  | // just-created record. This ensures that all PersistentSampleMap objects | 
|  | // will always use the same record, whichever was first made iterable. | 
|  | // Thread-safety within a process where multiple threads use the same | 
|  | // histogram object is delegated to the controlling histogram object which, | 
|  | // for sparse histograms, is a lock object. | 
|  | count_pointer = ImportSamples(value, false); | 
|  | DCHECK(count_pointer); | 
|  | return count_pointer; | 
|  | } | 
|  |  | 
|  | PersistentSampleMapRecords* PersistentSampleMap::GetRecords() { | 
|  | // The |records_| pointer is lazily fetched from the |allocator_| only on | 
|  | // first use. Sometimes duplicate histograms are created by race conditions | 
|  | // and if both were to grab the records object, there would be a conflict. | 
|  | // Use of a histogram, and thus a call to this method, won't occur until | 
|  | // after the histogram has been de-dup'd. | 
|  | if (!records_) | 
|  | records_ = allocator_->UseSampleMapRecords(id(), this); | 
|  | return records_; | 
|  | } | 
|  |  | 
|  | Count* PersistentSampleMap::ImportSamples(Sample until_value, | 
|  | bool import_everything) { | 
|  | Count* found_count = nullptr; | 
|  | PersistentMemoryAllocator::Reference ref; | 
|  | PersistentSampleMapRecords* records = GetRecords(); | 
|  | while ((ref = records->GetNext()) != 0) { | 
|  | SampleRecord* record = records->GetAsObject<SampleRecord>(ref); | 
|  | if (!record) | 
|  | continue; | 
|  |  | 
|  | DCHECK_EQ(id(), record->id); | 
|  |  | 
|  | // Check if the record's value is already known. | 
|  | if (!ContainsKey(sample_counts_, record->value)) { | 
|  | // No: Add it to map of known values. | 
|  | sample_counts_[record->value] = &record->count; | 
|  | } else { | 
|  | // Yes: Ignore it; it's a duplicate caused by a race condition -- see | 
|  | // code & comment in GetOrCreateSampleCountStorage() for details. | 
|  | // Check that nothing ever operated on the duplicate record. | 
|  | DCHECK_EQ(0, record->count); | 
|  | } | 
|  |  | 
|  | // Check if it's the value being searched for and, if so, keep a pointer | 
|  | // to return later. Stop here unless everything is being imported. | 
|  | // Because race conditions can cause multiple records for a single value, | 
|  | // be sure to return the first one found. | 
|  | if (record->value == until_value) { | 
|  | if (!found_count) | 
|  | found_count = &record->count; | 
|  | if (!import_everything) | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | return found_count; | 
|  | } | 
|  |  | 
|  | }  // namespace base |