1 // Copyright 2016 The Chromium Authors. All rights reserved.
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/persistent_sample_map.h"
6
7 #include "base/logging.h"
8 #include "base/memory/ptr_util.h"
9 #include "base/metrics/persistent_histogram_allocator.h"
10 #include "base/stl_util.h"
11
12 namespace base {
13
14 typedef HistogramBase::Count Count;
15 typedef HistogramBase::Sample Sample;
16
17 namespace {
18
19 // An iterator for going through a PersistentSampleMap. The logic here is
20 // identical to that of SampleMapIterator but with different data structures.
21 // Changes here likely need to be duplicated there.
22 class PersistentSampleMapIterator : public SampleCountIterator {
23 public:
24 typedef std::map<HistogramBase::Sample, HistogramBase::Count*>
25 SampleToCountMap;
26
27 explicit PersistentSampleMapIterator(const SampleToCountMap& sample_counts);
28 ~PersistentSampleMapIterator() override;
29
30 // SampleCountIterator:
31 bool Done() const override;
32 void Next() override;
33 void Get(HistogramBase::Sample* min,
34 HistogramBase::Sample* max,
35 HistogramBase::Count* count) const override;
36
37 private:
38 void SkipEmptyBuckets();
39
40 SampleToCountMap::const_iterator iter_;
41 const SampleToCountMap::const_iterator end_;
42 };
43
PersistentSampleMapIterator(const SampleToCountMap & sample_counts)44 PersistentSampleMapIterator::PersistentSampleMapIterator(
45 const SampleToCountMap& sample_counts)
46 : iter_(sample_counts.begin()),
47 end_(sample_counts.end()) {
48 SkipEmptyBuckets();
49 }
50
~PersistentSampleMapIterator()51 PersistentSampleMapIterator::~PersistentSampleMapIterator() {}
52
Done() const53 bool PersistentSampleMapIterator::Done() const {
54 return iter_ == end_;
55 }
56
Next()57 void PersistentSampleMapIterator::Next() {
58 DCHECK(!Done());
59 ++iter_;
60 SkipEmptyBuckets();
61 }
62
Get(Sample * min,Sample * max,Count * count) const63 void PersistentSampleMapIterator::Get(Sample* min,
64 Sample* max,
65 Count* count) const {
66 DCHECK(!Done());
67 if (min)
68 *min = iter_->first;
69 if (max)
70 *max = iter_->first + 1;
71 if (count)
72 *count = *iter_->second;
73 }
74
SkipEmptyBuckets()75 void PersistentSampleMapIterator::SkipEmptyBuckets() {
76 while (!Done() && *iter_->second == 0) {
77 ++iter_;
78 }
79 }
80
81 // This structure holds an entry for a PersistentSampleMap within a persistent
82 // memory allocator. The "id" must be unique across all maps held by an
83 // allocator or they will get attached to the wrong sample map.
84 struct SampleRecord {
85 uint64_t id; // Unique identifier of owner.
86 Sample value; // The value for which this record holds a count.
87 Count count; // The count associated with the above value.
88 };
89
90 // The type-id used to identify sample records inside an allocator.
91 const uint32_t kTypeIdSampleRecord = 0x8FE6A69F + 1; // SHA1(SampleRecord) v1
92
93 } // namespace
94
PersistentSampleMap(uint64_t id,PersistentHistogramAllocator * allocator,Metadata * meta)95 PersistentSampleMap::PersistentSampleMap(
96 uint64_t id,
97 PersistentHistogramAllocator* allocator,
98 Metadata* meta)
99 : HistogramSamples(id, meta), allocator_(allocator) {}
100
~PersistentSampleMap()101 PersistentSampleMap::~PersistentSampleMap() {
102 if (records_)
103 records_->Release(this);
104 }
105
Accumulate(Sample value,Count count)106 void PersistentSampleMap::Accumulate(Sample value, Count count) {
107 *GetOrCreateSampleCountStorage(value) += count;
108 IncreaseSum(static_cast<int64_t>(count) * value);
109 IncreaseRedundantCount(count);
110 }
111
GetCount(Sample value) const112 Count PersistentSampleMap::GetCount(Sample value) const {
113 // Have to override "const" to make sure all samples have been loaded before
114 // being able to know what value to return.
115 Count* count_pointer =
116 const_cast<PersistentSampleMap*>(this)->GetSampleCountStorage(value);
117 return count_pointer ? *count_pointer : 0;
118 }
119
TotalCount() const120 Count PersistentSampleMap::TotalCount() const {
121 // Have to override "const" in order to make sure all samples have been
122 // loaded before trying to iterate over the map.
123 const_cast<PersistentSampleMap*>(this)->ImportSamples(-1, true);
124
125 Count count = 0;
126 for (const auto& entry : sample_counts_) {
127 count += *entry.second;
128 }
129 return count;
130 }
131
Iterator() const132 std::unique_ptr<SampleCountIterator> PersistentSampleMap::Iterator() const {
133 // Have to override "const" in order to make sure all samples have been
134 // loaded before trying to iterate over the map.
135 const_cast<PersistentSampleMap*>(this)->ImportSamples(-1, true);
136 return WrapUnique(new PersistentSampleMapIterator(sample_counts_));
137 }
138
139 // static
140 PersistentMemoryAllocator::Reference
GetNextPersistentRecord(PersistentMemoryAllocator::Iterator & iterator,uint64_t * sample_map_id)141 PersistentSampleMap::GetNextPersistentRecord(
142 PersistentMemoryAllocator::Iterator& iterator,
143 uint64_t* sample_map_id) {
144 PersistentMemoryAllocator::Reference ref =
145 iterator.GetNextOfType(kTypeIdSampleRecord);
146 const SampleRecord* record =
147 iterator.GetAsObject<SampleRecord>(ref, kTypeIdSampleRecord);
148 if (!record)
149 return 0;
150
151 *sample_map_id = record->id;
152 return ref;
153 }
154
155 // static
156 PersistentMemoryAllocator::Reference
CreatePersistentRecord(PersistentMemoryAllocator * allocator,uint64_t sample_map_id,Sample value)157 PersistentSampleMap::CreatePersistentRecord(
158 PersistentMemoryAllocator* allocator,
159 uint64_t sample_map_id,
160 Sample value) {
161 PersistentMemoryAllocator::Reference ref =
162 allocator->Allocate(sizeof(SampleRecord), kTypeIdSampleRecord);
163 SampleRecord* record =
164 allocator->GetAsObject<SampleRecord>(ref, kTypeIdSampleRecord);
165
166 if (!record) {
167 NOTREACHED() << "full=" << allocator->IsFull()
168 << ", corrupt=" << allocator->IsCorrupt();
169 return 0;
170 }
171
172 record->id = sample_map_id;
173 record->value = value;
174 record->count = 0;
175 allocator->MakeIterable(ref);
176 return ref;
177 }
178
AddSubtractImpl(SampleCountIterator * iter,Operator op)179 bool PersistentSampleMap::AddSubtractImpl(SampleCountIterator* iter,
180 Operator op) {
181 Sample min;
182 Sample max;
183 Count count;
184 for (; !iter->Done(); iter->Next()) {
185 iter->Get(&min, &max, &count);
186 if (min + 1 != max)
187 return false; // SparseHistogram only supports bucket with size 1.
188
189 *GetOrCreateSampleCountStorage(min) +=
190 (op == HistogramSamples::ADD) ? count : -count;
191 }
192 return true;
193 }
194
GetSampleCountStorage(Sample value)195 Count* PersistentSampleMap::GetSampleCountStorage(Sample value) {
196 // If |value| is already in the map, just return that.
197 auto it = sample_counts_.find(value);
198 if (it != sample_counts_.end())
199 return it->second;
200
201 // Import any new samples from persistent memory looking for the value.
202 return ImportSamples(value, false);
203 }
204
GetOrCreateSampleCountStorage(Sample value)205 Count* PersistentSampleMap::GetOrCreateSampleCountStorage(Sample value) {
206 // Get any existing count storage.
207 Count* count_pointer = GetSampleCountStorage(value);
208 if (count_pointer)
209 return count_pointer;
210
211 // Create a new record in persistent memory for the value. |records_| will
212 // have been initialized by the GetSampleCountStorage() call above.
213 DCHECK(records_);
214 PersistentMemoryAllocator::Reference ref = records_->CreateNew(value);
215 if (!ref) {
216 // If a new record could not be created then the underlying allocator is
217 // full or corrupt. Instead, allocate the counter from the heap. This
218 // sample will not be persistent, will not be shared, and will leak...
219 // but it's better than crashing.
220 count_pointer = new Count(0);
221 sample_counts_[value] = count_pointer;
222 return count_pointer;
223 }
224
225 // A race condition between two independent processes (i.e. two independent
226 // histogram objects sharing the same sample data) could cause two of the
227 // above records to be created. The allocator, however, forces a strict
228 // ordering on iterable objects so use the import method to actually add the
229 // just-created record. This ensures that all PersistentSampleMap objects
230 // will always use the same record, whichever was first made iterable.
231 // Thread-safety within a process where multiple threads use the same
232 // histogram object is delegated to the controlling histogram object which,
233 // for sparse histograms, is a lock object.
234 count_pointer = ImportSamples(value, false);
235 DCHECK(count_pointer);
236 return count_pointer;
237 }
238
GetRecords()239 PersistentSampleMapRecords* PersistentSampleMap::GetRecords() {
240 // The |records_| pointer is lazily fetched from the |allocator_| only on
241 // first use. Sometimes duplicate histograms are created by race conditions
242 // and if both were to grab the records object, there would be a conflict.
243 // Use of a histogram, and thus a call to this method, won't occur until
244 // after the histogram has been de-dup'd.
245 if (!records_)
246 records_ = allocator_->UseSampleMapRecords(id(), this);
247 return records_;
248 }
249
ImportSamples(Sample until_value,bool import_everything)250 Count* PersistentSampleMap::ImportSamples(Sample until_value,
251 bool import_everything) {
252 Count* found_count = nullptr;
253 PersistentMemoryAllocator::Reference ref;
254 PersistentSampleMapRecords* records = GetRecords();
255 while ((ref = records->GetNext()) != 0) {
256 SampleRecord* record =
257 records->GetAsObject<SampleRecord>(ref, kTypeIdSampleRecord);
258 if (!record)
259 continue;
260
261 DCHECK_EQ(id(), record->id);
262
263 // Check if the record's value is already known.
264 if (!ContainsKey(sample_counts_, record->value)) {
265 // No: Add it to map of known values.
266 sample_counts_[record->value] = &record->count;
267 } else {
268 // Yes: Ignore it; it's a duplicate caused by a race condition -- see
269 // code & comment in GetOrCreateSampleCountStorage() for details.
270 // Check that nothing ever operated on the duplicate record.
271 DCHECK_EQ(0, record->count);
272 }
273
274 // Check if it's the value being searched for and, if so, keep a pointer
275 // to return later. Stop here unless everything is being imported.
276 // Because race conditions can cause multiple records for a single value,
277 // be sure to return the first one found.
278 if (record->value == until_value) {
279 if (!found_count)
280 found_count = &record->count;
281 if (!import_everything)
282 break;
283 }
284 }
285
286 return found_count;
287 }
288
289 } // namespace base
290