Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(28)

Side by Side Diff: base/metrics/sample_vector.cc

Issue 2811713003: Embed a single sample in histogram metadata. (Closed)
Patch Set: move single-sample from 'value' to 'bucket' Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "base/metrics/sample_vector.h" 5 #include "base/metrics/sample_vector.h"
6 6
7 #include "base/lazy_instance.h"
7 #include "base/logging.h" 8 #include "base/logging.h"
9 #include "base/memory/ptr_util.h"
8 #include "base/metrics/bucket_ranges.h" 10 #include "base/metrics/bucket_ranges.h"
11 #include "base/metrics/persistent_memory_allocator.h"
12 #include "base/synchronization/lock.h"
13 #include "base/threading/platform_thread.h"
14
15 // This SampleVector makes use of the single-sample embedded in the base
16 // HistogramSamples class. If the count is non-zero then there is guaranteed
17 // (within the bounds of "eventual consistency") to be no allocated external
18 // storage. Once the full counts storage is allocated, the single-sample must
19 // be extracted and disabled.
9 20
10 namespace base { 21 namespace base {
11 22
12 typedef HistogramBase::Count Count; 23 typedef HistogramBase::Count Count;
13 typedef HistogramBase::Sample Sample; 24 typedef HistogramBase::Sample Sample;
14 25
15 SampleVector::SampleVector(const BucketRanges* bucket_ranges) 26 SampleVectorBase::SampleVectorBase(uint64_t id,
16 : SampleVector(0, bucket_ranges) {} 27 const BucketRanges* bucket_ranges)
17
18 SampleVector::SampleVector(uint64_t id, const BucketRanges* bucket_ranges)
19 : HistogramSamples(id), 28 : HistogramSamples(id),
20 local_counts_(bucket_ranges->bucket_count()), 29 counts_size_(bucket_ranges->bucket_count()),
21 counts_(&local_counts_[0]),
22 counts_size_(local_counts_.size()),
23 bucket_ranges_(bucket_ranges) { 30 bucket_ranges_(bucket_ranges) {
24 CHECK_GE(bucket_ranges_->bucket_count(), 1u); 31 CHECK_GE(bucket_ranges_->bucket_count(), 1u);
25 } 32 }
26 33
27 SampleVector::SampleVector(uint64_t id, 34 SampleVectorBase::SampleVectorBase(uint64_t id,
28 HistogramBase::AtomicCount* counts, 35 Metadata* meta,
29 size_t counts_size, 36 const BucketRanges* bucket_ranges)
30 Metadata* meta,
31 const BucketRanges* bucket_ranges)
32 : HistogramSamples(id, meta), 37 : HistogramSamples(id, meta),
33 counts_(counts),
34 counts_size_(bucket_ranges->bucket_count()), 38 counts_size_(bucket_ranges->bucket_count()),
35 bucket_ranges_(bucket_ranges) { 39 bucket_ranges_(bucket_ranges) {
36 CHECK_LE(bucket_ranges_->bucket_count(), counts_size_);
37 CHECK_GE(bucket_ranges_->bucket_count(), 1u); 40 CHECK_GE(bucket_ranges_->bucket_count(), 1u);
38 } 41 }
39 42
40 SampleVector::~SampleVector() {} 43 SampleVectorBase::~SampleVectorBase() {}
41 44
42 void SampleVector::Accumulate(Sample value, Count count) { 45 void SampleVectorBase::Accumulate(Sample value, Count count) {
43 size_t bucket_index = GetBucketIndex(value); 46 const size_t bucket_index = GetBucketIndex(value);
44 subtle::NoBarrier_AtomicIncrement(&counts_[bucket_index], count); 47
45 IncreaseSum(static_cast<int64_t>(count) * value); 48 // Handle the single-sample case.
46 IncreaseRedundantCount(count); 49 if (!counts()) {
47 } 50 // Try to accumulate the parameters into the single-count entry.
48 51 if (AccumulateSingleSample(value, count, bucket_index)) {
49 Count SampleVector::GetCount(Sample value) const { 52 // A race condition could lead to a new single-sample being accumulated
50 size_t bucket_index = GetBucketIndex(value); 53 // above just after another thread executed the MountCountsStorage below.
51 return subtle::NoBarrier_Load(&counts_[bucket_index]); 54 // Since it is mounted, it could be mounted elsewhere and have values
52 } 55 // written to it. It's not allowed to have both a single-sample and
53 56 // entries in the counts array so move the single-sample.
54 Count SampleVector::TotalCount() const { 57 if (counts())
Alexei Svitkine (slow) 2017/04/20 19:56:13 Maybe we should rename counts() to GetCounts() - a
bcwhite 2017/04/20 21:18:08 I still see it as a simple accessor; it's just ret
55 Count count = 0; 58 MoveSingleSampleToCounts();
56 for (size_t i = 0; i < counts_size_; i++) { 59 return;
57 count += subtle::NoBarrier_Load(&counts_[i]); 60 }
58 } 61
59 return count; 62 // Need real storage to store both what was in the single-sample plus the
60 } 63 // parameter information.
61 64 MountCountsStorageAndMoveSingleSample();
62 Count SampleVector::GetCountAtIndex(size_t bucket_index) const { 65 }
66
67 // Handle the multi-sample case.
68 subtle::NoBarrier_AtomicIncrement(&counts()[bucket_index], count);
69 IncreaseSumAndCount(static_cast<int64_t>(count) * value, count);
70 }
71
72 Count SampleVectorBase::GetCount(Sample value) const {
73 return GetCountAtIndex(GetBucketIndex(value));
74 }
75
76 Count SampleVectorBase::TotalCount() const {
77 // Handle the single-sample case.
78 SingleSample sample = single_sample().Load();
79 if (sample.count != 0)
80 return sample.count;
81
82 // Handle the multi-sample case.
83 if (counts() || MountExistingCountsStorage()) {
84 Count count = 0;
85 const HistogramBase::AtomicCount* counts_array = counts();
86 for (size_t i = 0; i < counts_size_; i++) {
87 count += subtle::NoBarrier_Load(&counts_array[i]);
88 }
89 return count;
90 }
91
92 // And the no-value case.
93 return 0;
94 }
95
96 Count SampleVectorBase::GetCountAtIndex(size_t bucket_index) const {
63 DCHECK(bucket_index < counts_size_); 97 DCHECK(bucket_index < counts_size_);
64 return subtle::NoBarrier_Load(&counts_[bucket_index]); 98
65 } 99 // Handle the single-sample case.
66 100 SingleSample sample = single_sample().Load();
67 std::unique_ptr<SampleCountIterator> SampleVector::Iterator() const { 101 if (sample.count != 0)
68 return std::unique_ptr<SampleCountIterator>( 102 return sample.bucket == bucket_index ? sample.count : 0;
69 new SampleVectorIterator(counts_, counts_size_, bucket_ranges_)); 103
70 } 104 // Handle the multi-sample case.
71 105 if (counts() || MountExistingCountsStorage())
72 bool SampleVector::AddSubtractImpl(SampleCountIterator* iter, 106 return subtle::NoBarrier_Load(&counts()[bucket_index]);
73 HistogramSamples::Operator op) { 107
108 // And the no-value case.
109 return 0;
110 }
111
112 std::unique_ptr<SampleCountIterator> SampleVectorBase::Iterator() const {
113 // Handle the single-sample case.
114 SingleSample sample = single_sample().Load();
115 if (sample.count != 0) {
116 return MakeUnique<SingleSampleIterator>(
117 bucket_ranges_->range(sample.bucket),
118 bucket_ranges_->range(sample.bucket + 1), sample.count, sample.bucket);
119 }
120
121 // Handle the multi-sample case.
122 if (counts() || MountExistingCountsStorage()) {
123 return MakeUnique<SampleVectorIterator>(counts(), counts_size_,
124 bucket_ranges_);
125 }
126
127 // And the no-value case.
128 return MakeUnique<SampleVectorIterator>(nullptr, 0, bucket_ranges_);
129 }
130
131 bool SampleVectorBase::AddSubtractImpl(SampleCountIterator* iter,
132 HistogramSamples::Operator op) {
74 HistogramBase::Sample min; 133 HistogramBase::Sample min;
75 HistogramBase::Sample max; 134 HistogramBase::Sample max;
76 HistogramBase::Count count; 135 HistogramBase::Count count;
136 size_t index_offset = 0;
137 size_t iter_index;
138 size_t dest_index;
139
140 // Stop now if there's nothing to do.
141 if (iter->Done())
142 return true;
143
144 // Get the first value and its index.
145 iter->Get(&min, &max, &count);
146 dest_index = GetBucketIndex(min);
147
148 // The destination must be a superset of the source meaning that though the
149 // incoming ranges will find an exact match, the incoming bucket-index, if
150 // it exists, may be offset. Calculate that offset if the passed iterator;
151 // there are are no overflow checks because 2's compliment math will work it
152 // out in the end.
153 if (iter->GetBucketIndex(&iter_index))
Alexei Svitkine (slow) 2017/04/20 19:56:12 What happens if this returns false? I don't see th
bcwhite 2017/04/20 21:18:08 Done.
154 index_offset = dest_index - iter_index;
Alexei Svitkine (slow) 2017/04/20 19:56:11 index_offset isn't used until the while() loop at
bcwhite 2017/04/20 21:18:08 GetBucketIndex() has to be called before Next() an
Alexei Svitkine (slow) 2017/04/20 22:11:34 Ah! Thanks for expanding the comment to mention th
155 if (dest_index >= counts_size_)
156 return false;
157
158 // Post-increment.
159 iter->Next();
160
161 // Single-value storage is possible if there is no counts storage and the
162 // retrieved entry is the only one in the iterator.
163 if (!counts()) {
164 if (iter->Done()) {
165 // Don't call AccumulateSingleSample because that updates sum and count
Alexei Svitkine (slow) 2017/04/20 19:56:14 How about adding a param to AccumulateSingleSample
bcwhite 2017/04/20 21:18:07 I think that would be confusing as there is no "va
Alexei Svitkine (slow) 2017/04/20 22:11:34 I see. I still see this duplication of logic betwe
bcwhite 2017/04/21 13:25:31 That's part of it (since it's a generic concept) b
166 // which was already done by the caller of this method.
167 if (single_sample().Accumulate(
168 dest_index, op == HistogramSamples::ADD ? count : -count)) {
169 // Handle race-condition that mounted counts storage between above and
170 // here.
171 if (counts())
172 MoveSingleSampleToCounts();
173 return true;
174 }
175 }
176
177 // The counts storage will be needed to hold the multiple incoming values.
178 MountCountsStorageAndMoveSingleSample();
179 }
77 180
78 // Go through the iterator and add the counts into correct bucket. 181 // Go through the iterator and add the counts into correct bucket.
79 size_t index = 0; 182 while (true) {
80 while (index < counts_size_ && !iter->Done()) { 183 // Ensure that the sample's min/max match the ranges min/max.
184 if (min != bucket_ranges_->range(dest_index) ||
185 max != bucket_ranges_->range(dest_index + 1)) {
186 NOTREACHED() << "sample=" << min << "," << max
187 << "; range=" << bucket_ranges_->range(dest_index) << ","
188 << bucket_ranges_->range(dest_index + 1);
189 return false;
190 }
191
192 // Sample's bucket matches exactly. Adjust count.
193 subtle::NoBarrier_AtomicIncrement(
194 &counts()[dest_index], op == HistogramSamples::ADD ? count : -count);
195
196 // Advance to the next iterable sample. See comments above for how
197 // everything works.
198 if (iter->Done())
199 return true;
81 iter->Get(&min, &max, &count); 200 iter->Get(&min, &max, &count);
82 if (min == bucket_ranges_->range(index) && 201 if (iter->GetBucketIndex(&iter_index)) {
83 max == bucket_ranges_->range(index + 1)) { 202 // Destination bucket is a known offset from the source bucket.
84 // Sample matches this bucket! 203 dest_index = iter_index + index_offset;
85 subtle::NoBarrier_AtomicIncrement(
86 &counts_[index], op == HistogramSamples::ADD ? count : -count);
87 iter->Next();
88 } else if (min > bucket_ranges_->range(index)) {
89 // Sample is larger than current bucket range. Try next.
90 index++;
91 } else { 204 } else {
92 // Sample is smaller than current bucket range. We scan buckets from 205 // Destination bucket has to be determined anew each time.
93 // smallest to largest, so the sample value must be invalid. 206 dest_index = GetBucketIndex(min);
207 }
208 if (dest_index >= counts_size_)
94 return false; 209 return false;
95 } 210 iter->Next();
96 } 211 }
97
98 return iter->Done();
99 } 212 }
100 213
101 // Use simple binary search. This is very general, but there are better 214 // Use simple binary search. This is very general, but there are better
102 // approaches if we knew that the buckets were linearly distributed. 215 // approaches if we knew that the buckets were linearly distributed.
103 size_t SampleVector::GetBucketIndex(Sample value) const { 216 size_t SampleVectorBase::GetBucketIndex(Sample value) const {
104 size_t bucket_count = bucket_ranges_->bucket_count(); 217 size_t bucket_count = bucket_ranges_->bucket_count();
105 CHECK_GE(bucket_count, 1u); 218 CHECK_GE(bucket_count, 1u);
106 CHECK_GE(value, bucket_ranges_->range(0)); 219 CHECK_GE(value, bucket_ranges_->range(0));
107 CHECK_LT(value, bucket_ranges_->range(bucket_count)); 220 CHECK_LT(value, bucket_ranges_->range(bucket_count));
108 221
109 size_t under = 0; 222 size_t under = 0;
110 size_t over = bucket_count; 223 size_t over = bucket_count;
111 size_t mid; 224 size_t mid;
112 do { 225 do {
113 DCHECK_GE(over, under); 226 DCHECK_GE(over, under);
114 mid = under + (over - under)/2; 227 mid = under + (over - under)/2;
115 if (mid == under) 228 if (mid == under)
116 break; 229 break;
117 if (bucket_ranges_->range(mid) <= value) 230 if (bucket_ranges_->range(mid) <= value)
118 under = mid; 231 under = mid;
119 else 232 else
120 over = mid; 233 over = mid;
121 } while (true); 234 } while (true);
122 235
123 DCHECK_LE(bucket_ranges_->range(mid), value); 236 DCHECK_LE(bucket_ranges_->range(mid), value);
124 CHECK_GT(bucket_ranges_->range(mid + 1), value); 237 CHECK_GT(bucket_ranges_->range(mid + 1), value);
125 return mid; 238 return mid;
126 } 239 }
127 240
241 void SampleVectorBase::MoveSingleSampleToCounts() {
242 DCHECK(counts());
243
244 // Disable the single-sample since there is now counts storage for the data.
245 SingleSample sample = single_sample().Extract(/*disable=*/true);
246
247 // Stop here if there is no "count" as trying to find the bucket index of
248 // an invalid (including zero) "value" will crash.
249 if (sample.count == 0)
250 return;
251
252 // Move the value into storage. Sum and redundant-count already account
253 // for this entry so no need to call IncreaseSumAndCount().
254 subtle::NoBarrier_AtomicIncrement(&counts()[sample.bucket], sample.count);
255 }
256
257 void SampleVectorBase::MountCountsStorageAndMoveSingleSample() {
258 // There are many SampleVector objects and the lock is needed very
259 // infrequently (just when advancing from single-sample to multi-sample) so
260 // define a single, global lock that all can use. This lock only prevents
261 // concurrent entry into the code below; access and updates to |counts_|
262 // still requires atomic operations.
263 static LazyInstance<Lock>::Leaky counts_lock = LAZY_INSTANCE_INITIALIZER;
264 if (subtle::NoBarrier_Load(&counts_) == 0) {
265 AutoLock lock(counts_lock.Get());
266 if (subtle::NoBarrier_Load(&counts_) == 0) {
267 // Create the actual counts storage while the above lock is acquired.
268 HistogramBase::Count* counts = CreateCountsStorageWhileLocked();
269 DCHECK(counts);
270
271 // Point |counts_| to the newly created storage. This is done while
272 // locked to prevent possible concurrent calls to CreateCountsStorage
273 // but, between that call and here, other threads could notice the
274 // existance of the storage and race with this to set_counts(). That's
275 // okay because (a) it's atomic and (b) it always writes the same value.
276 set_counts(counts);
277 }
278 }
279
280 // Move any single-sample into the newly mounted storage.
281 MoveSingleSampleToCounts();
282 }
283
284 SampleVector::SampleVector(const BucketRanges* bucket_ranges)
285 : SampleVector(0, bucket_ranges) {}
286
287 SampleVector::SampleVector(uint64_t id, const BucketRanges* bucket_ranges)
288 : SampleVectorBase(id, bucket_ranges) {}
289
290 SampleVector::~SampleVector() {}
291
292 bool SampleVector::MountExistingCountsStorage() const {
293 // There is never any existing storage other than what is already in use.
294 return counts() != nullptr;
295 }
296
297 HistogramBase::AtomicCount* SampleVector::CreateCountsStorageWhileLocked() {
298 local_counts_.resize(counts_size());
299 return &local_counts_[0];
300 }
301
302 PersistentSampleVector::PersistentSampleVector(
303 uint64_t id,
304 const BucketRanges* bucket_ranges,
305 Metadata* meta,
306 const DelayedPersistentAllocation& counts)
307 : SampleVectorBase(id, meta, bucket_ranges), persistent_counts_(counts) {
308 // Only mount the full storage if the single-sample has been disabled.
309 // Otherwise, it is possible for this object instance to start using (empty)
310 // storage that was created incidentally (because it shares a memory segment
311 // with another block) while another instance continues to update to the
312 // single sample.
313 if (single_sample().IsDisabled()) {
314 bool success = MountExistingCountsStorage();
315 DCHECK(success);
316 }
317 }
318
319 PersistentSampleVector::~PersistentSampleVector() {}
320
321 bool PersistentSampleVector::MountExistingCountsStorage() const {
322 // There is no check that counts is not yet mounted because, given that this
323 // is a virtual function, it's more efficient to do that at the call-site.
324 // There is no danger, however, because at worst the counts value would be
325 // overwritten (in an atomic manner) with the exact same address.
326
327 if (!persistent_counts_.reference())
328 return false; // Nothing to mount.
329
330 // Mount the counts array in position.
331 set_counts(
332 static_cast<HistogramBase::AtomicCount*>(persistent_counts_.Get()));
333 return true;
334 }
335
336 HistogramBase::AtomicCount*
337 PersistentSampleVector::CreateCountsStorageWhileLocked() {
338 void* mem = persistent_counts_.Get();
339 if (!mem) {
340 // The above shouldn't fail but can if Bad Things(tm) are occurring in the
341 // persistent allocator. Crashing isn't a good option so instead just
342 // allocate something from the heap and return that. There will be no
343 // sharing or persistence but worse things are already happening.
344 return new HistogramBase::AtomicCount[counts_size()];
345 }
346
347 return static_cast<HistogramBase::AtomicCount*>(mem);
348 }
349
128 SampleVectorIterator::SampleVectorIterator( 350 SampleVectorIterator::SampleVectorIterator(
129 const std::vector<HistogramBase::AtomicCount>* counts, 351 const std::vector<HistogramBase::AtomicCount>* counts,
130 const BucketRanges* bucket_ranges) 352 const BucketRanges* bucket_ranges)
131 : counts_(&(*counts)[0]), 353 : counts_(&(*counts)[0]),
132 counts_size_(counts->size()), 354 counts_size_(counts->size()),
133 bucket_ranges_(bucket_ranges), 355 bucket_ranges_(bucket_ranges),
134 index_(0) { 356 index_(0) {
135 CHECK_GE(bucket_ranges_->bucket_count(), counts_size_); 357 CHECK_GE(bucket_ranges_->bucket_count(), counts_size_);
136 SkipEmptyBuckets(); 358 SkipEmptyBuckets();
137 } 359 }
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
184 return; 406 return;
185 407
186 while (index_ < counts_size_) { 408 while (index_ < counts_size_) {
187 if (subtle::NoBarrier_Load(&counts_[index_]) != 0) 409 if (subtle::NoBarrier_Load(&counts_[index_]) != 0)
188 return; 410 return;
189 index_++; 411 index_++;
190 } 412 }
191 } 413 }
192 414
193 } // namespace base 415 } // namespace base
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698