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