OLD | NEW |
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) { |
| 46 // Handle the single-sample case. |
| 47 if (!counts()) { |
| 48 // Try to accumulate the parameters into the single-count entry. |
| 49 if (AccumulateSingleSample(value, count)) { |
| 50 // A race condition could lead to a new single-sample being accumulated |
| 51 // above just after another thread executed the MountCountsStorage() |
| 52 // below. Since it is mounted, it could be mounted elsewhere and have |
| 53 // values written to it. It's not allowed to have both a single-sample |
| 54 // and entries in the counts array so move the single-sample. |
| 55 if (counts()) |
| 56 MoveSingleSampleToCounts(); |
| 57 |
| 58 return; |
| 59 } |
| 60 |
| 61 // Need real storage to store both what was in the single-sample plus the |
| 62 // parameter information. |
| 63 MountCountsStorage(); |
| 64 } |
| 65 |
| 66 // Handle the multi-sample case. |
43 size_t bucket_index = GetBucketIndex(value); | 67 size_t bucket_index = GetBucketIndex(value); |
44 subtle::NoBarrier_AtomicIncrement(&counts_[bucket_index], count); | 68 subtle::NoBarrier_AtomicIncrement(&counts()[bucket_index], count); |
45 IncreaseSum(static_cast<int64_t>(count) * value); | 69 IncreaseSumAndCount(static_cast<int64_t>(count) * value, count); |
46 IncreaseRedundantCount(count); | 70 } |
47 } | 71 |
48 | 72 Count SampleVectorBase::GetCount(Sample value) const { |
49 Count SampleVector::GetCount(Sample value) const { | 73 return GetCountAtIndex(GetBucketIndex(value)); |
50 size_t bucket_index = GetBucketIndex(value); | 74 } |
51 return subtle::NoBarrier_Load(&counts_[bucket_index]); | 75 |
52 } | 76 Count SampleVectorBase::TotalCount() const { |
53 | 77 // Handle the single-sample case. |
54 Count SampleVector::TotalCount() const { | 78 SingleSample sample = single_sample().Load(); |
55 Count count = 0; | 79 if (sample.count) |
56 for (size_t i = 0; i < counts_size_; i++) { | 80 return sample.count; |
57 count += subtle::NoBarrier_Load(&counts_[i]); | 81 |
58 } | 82 // Handle the multi-sample case. |
59 return count; | 83 if (counts() || MountExistingCountsStorage()) { |
60 } | 84 Count count = 0; |
61 | 85 const HistogramBase::AtomicCount* counts_array = counts(); |
62 Count SampleVector::GetCountAtIndex(size_t bucket_index) const { | 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 if (sample.value >= bucket_ranges_->range(bucket_index) && |
69 new SampleVectorIterator(counts_, counts_size_, bucket_ranges_)); | 103 sample.value < bucket_ranges_->range(bucket_index + 1)) { |
70 } | 104 return sample.count; |
71 | 105 } |
72 bool SampleVector::AddSubtractImpl(SampleCountIterator* iter, | 106 return 0; |
73 HistogramSamples::Operator op) { | 107 } |
| 108 |
| 109 // Handle the multi-sample case. |
| 110 if (counts() || MountExistingCountsStorage()) |
| 111 return subtle::NoBarrier_Load(&counts()[bucket_index]); |
| 112 |
| 113 // And the no-value case. |
| 114 return 0; |
| 115 } |
| 116 |
| 117 std::unique_ptr<SampleCountIterator> SampleVectorBase::Iterator() const { |
| 118 // Handle the single-sample case. |
| 119 SingleSample sample = single_sample().Load(); |
| 120 if (sample.count != 0) { |
| 121 size_t index = GetBucketIndex(sample.value); |
| 122 return MakeUnique<SingleSampleIterator>(bucket_ranges_->range(index), |
| 123 bucket_ranges_->range(index + 1), |
| 124 sample.count, index); |
| 125 } |
| 126 |
| 127 // Handle the multi-sample case. |
| 128 if (counts() || MountExistingCountsStorage()) { |
| 129 return MakeUnique<SampleVectorIterator>(counts(), counts_size_, |
| 130 bucket_ranges_); |
| 131 } |
| 132 |
| 133 // And the no-value case. |
| 134 return MakeUnique<SampleVectorIterator>(nullptr, 0, bucket_ranges_); |
| 135 } |
| 136 |
| 137 bool SampleVectorBase::AddSubtractImpl(SampleCountIterator* iter, |
| 138 HistogramSamples::Operator op) { |
74 HistogramBase::Sample min; | 139 HistogramBase::Sample min; |
75 HistogramBase::Sample max; | 140 HistogramBase::Sample max; |
76 HistogramBase::Count count; | 141 HistogramBase::Count count; |
77 | 142 |
| 143 // Stop now if there's nothing to do. |
| 144 if (iter->Done()) |
| 145 return true; |
| 146 |
| 147 // Get the first value. Single-value storage is possible if there is no |
| 148 // counts storage and the retrieved entry is the only one in the iterator. |
| 149 iter->Get(&min, &max, &count); |
| 150 if (!counts()) { |
| 151 if (iter->Done()) { |
| 152 // Don't call AccumulateSingleSample because that udpates sum and count |
| 153 // which was already done by the caller of this method. |
| 154 if (single_sample().Accumulate(min, count)) { |
| 155 if (!counts()) |
| 156 return true; |
| 157 |
| 158 // Handle race-condition creating counts during the accumulate. |
| 159 MoveSingleSampleToCounts(); |
| 160 |
| 161 // This will fall through to MountCountsStorage which is sub-optimal |
| 162 // (since we just detected that such has already been done) but it's |
| 163 // not worth optimizing because of the extreme rarity of this execution |
| 164 // path. |
| 165 } |
| 166 } |
| 167 |
| 168 // Definitely need the counts storage for what is to follow. |
| 169 MountCountsStorage(); |
| 170 } |
| 171 |
78 // Go through the iterator and add the counts into correct bucket. | 172 // Go through the iterator and add the counts into correct bucket. |
79 size_t index = 0; | 173 size_t index = 0; |
80 while (index < counts_size_ && !iter->Done()) { | 174 while (true) { |
| 175 while (index < counts_size_) { |
| 176 if (min == bucket_ranges_->range(index) && |
| 177 max == bucket_ranges_->range(index + 1)) { |
| 178 subtle::NoBarrier_AtomicIncrement( |
| 179 &counts()[index], op == HistogramSamples::ADD ? count : -count); |
| 180 break; |
| 181 } else if (min > bucket_ranges_->range(index)) { |
| 182 // Sample is larger than current bucket range. Try next. |
| 183 index++; |
| 184 } else { |
| 185 // Sample is smaller than current bucket range. We scan buckets from |
| 186 // smallest to largest, so the sample value must be invalid. |
| 187 return false; |
| 188 } |
| 189 } |
| 190 if (index == counts_size_) |
| 191 return false; |
| 192 |
| 193 iter->Next(); |
| 194 if (iter->Done()) |
| 195 return true; |
81 iter->Get(&min, &max, &count); | 196 iter->Get(&min, &max, &count); |
82 if (min == bucket_ranges_->range(index) && | 197 } |
83 max == bucket_ranges_->range(index + 1)) { | |
84 // Sample matches this bucket! | |
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 { | |
92 // Sample is smaller than current bucket range. We scan buckets from | |
93 // smallest to largest, so the sample value must be invalid. | |
94 return false; | |
95 } | |
96 } | |
97 | |
98 return iter->Done(); | |
99 } | 198 } |
100 | 199 |
101 // Use simple binary search. This is very general, but there are better | 200 // Use simple binary search. This is very general, but there are better |
102 // approaches if we knew that the buckets were linearly distributed. | 201 // approaches if we knew that the buckets were linearly distributed. |
103 size_t SampleVector::GetBucketIndex(Sample value) const { | 202 size_t SampleVectorBase::GetBucketIndex(Sample value) const { |
104 size_t bucket_count = bucket_ranges_->bucket_count(); | 203 size_t bucket_count = bucket_ranges_->bucket_count(); |
105 CHECK_GE(bucket_count, 1u); | 204 CHECK_GE(bucket_count, 1u); |
106 CHECK_GE(value, bucket_ranges_->range(0)); | 205 CHECK_GE(value, bucket_ranges_->range(0)); |
107 CHECK_LT(value, bucket_ranges_->range(bucket_count)); | 206 CHECK_LT(value, bucket_ranges_->range(bucket_count)); |
108 | 207 |
109 size_t under = 0; | 208 size_t under = 0; |
110 size_t over = bucket_count; | 209 size_t over = bucket_count; |
111 size_t mid; | 210 size_t mid; |
112 do { | 211 do { |
113 DCHECK_GE(over, under); | 212 DCHECK_GE(over, under); |
114 mid = under + (over - under)/2; | 213 mid = under + (over - under)/2; |
115 if (mid == under) | 214 if (mid == under) |
116 break; | 215 break; |
117 if (bucket_ranges_->range(mid) <= value) | 216 if (bucket_ranges_->range(mid) <= value) |
118 under = mid; | 217 under = mid; |
119 else | 218 else |
120 over = mid; | 219 over = mid; |
121 } while (true); | 220 } while (true); |
122 | 221 |
123 DCHECK_LE(bucket_ranges_->range(mid), value); | 222 DCHECK_LE(bucket_ranges_->range(mid), value); |
124 CHECK_GT(bucket_ranges_->range(mid + 1), value); | 223 CHECK_GT(bucket_ranges_->range(mid + 1), value); |
125 return mid; | 224 return mid; |
126 } | 225 } |
127 | 226 |
| 227 void SampleVectorBase::MoveSingleSampleToCounts() { |
| 228 DCHECK(counts()); |
| 229 // Disable the single-sample since there is now counts storage for the data. |
| 230 SingleSample sample = single_sample().Extract(/*disable=*/true); |
| 231 |
| 232 // Stop here if there is no "count" as trying to find the bucket index of |
| 233 // a invalid (including zero) "value" will crash. |
| 234 if (sample.count == 0) |
| 235 return; |
| 236 |
| 237 // Move the value into storage. Sum and redundant-count already account |
| 238 // for this entry so no need to call IncreaseSumAndCount(). |
| 239 size_t bucket_index = GetBucketIndex(sample.value); |
| 240 subtle::NoBarrier_AtomicIncrement(&counts()[bucket_index], sample.count); |
| 241 } |
| 242 |
| 243 void SampleVectorBase::MountCountsStorage() { |
| 244 // There are many of SampleVector objects and the lock is needed very |
| 245 // infrequently (just when advancing from single-sample to multi-sample) so |
| 246 // define a single, global lock that all can use. This lock only prevents |
| 247 // concurrent entry into the code below; access and updates to |counts_| |
| 248 // still requires atomic operations. |
| 249 static LazyInstance<Lock>::Leaky counts_lock = LAZY_INSTANCE_INITIALIZER; |
| 250 { |
| 251 AutoLock lock(counts_lock.Get()); |
| 252 if (subtle::NoBarrier_Load(&counts_) == 0) { |
| 253 // Create the actual counts storage while the above lock is acquired. |
| 254 HistogramBase::Count* counts = CreateCountsStorageWhileLocked(); |
| 255 DCHECK(counts); |
| 256 |
| 257 // Point |counts_| to the newly created storage. This is done while |
| 258 // locked to prevent possible concurrent calls to CreateCountsStorage |
| 259 // but, between that call and here, other threads could notice the |
| 260 // existance of the storage and race with this to set_counts(). That's |
| 261 // okay because (a) it's atomic and (b) it always writes the same value. |
| 262 set_counts(counts); |
| 263 } |
| 264 } |
| 265 |
| 266 // Move any single-sample into the newly mounted storage. |
| 267 MoveSingleSampleToCounts(); |
| 268 } |
| 269 |
| 270 SampleVector::SampleVector(const BucketRanges* bucket_ranges) |
| 271 : SampleVector(0, bucket_ranges) {} |
| 272 |
| 273 SampleVector::SampleVector(uint64_t id, const BucketRanges* bucket_ranges) |
| 274 : SampleVectorBase(id, bucket_ranges) {} |
| 275 |
| 276 SampleVector::~SampleVector() {} |
| 277 |
| 278 bool SampleVector::MountExistingCountsStorage() const { |
| 279 // There is never any existing storage other than what is already in use. |
| 280 return counts() != nullptr; |
| 281 } |
| 282 |
| 283 HistogramBase::AtomicCount* SampleVector::CreateCountsStorageWhileLocked() { |
| 284 local_counts_.resize(counts_size()); |
| 285 return &local_counts_[0]; |
| 286 } |
| 287 |
| 288 PersistentSampleVector::PersistentSampleVector( |
| 289 uint64_t id, |
| 290 const BucketRanges* bucket_ranges, |
| 291 Metadata* meta, |
| 292 const DelayedPersistentAllocation& counts) |
| 293 : SampleVectorBase(id, meta, bucket_ranges), persistent_counts_(counts) { |
| 294 // If the "counts" array has already been created, mount it now. |
| 295 if (persistent_counts_.reference()) |
| 296 set_counts( |
| 297 static_cast<HistogramBase::AtomicCount*>(persistent_counts_.Get())); |
| 298 } |
| 299 |
| 300 PersistentSampleVector::~PersistentSampleVector() {} |
| 301 |
| 302 bool PersistentSampleVector::MountExistingCountsStorage() const { |
| 303 // There is no check that counts is not yet mounted because, given that this |
| 304 // is a virtual function, it's more efficient to do that at the call-site. |
| 305 // There is no danger, however, because at worst the counts value would be |
| 306 // overwritten (in an atomic manner) with the exact same address. |
| 307 |
| 308 if (!persistent_counts_.reference()) |
| 309 return false; // Nothing to mount. |
| 310 |
| 311 // Mount the counts array in position. |
| 312 set_counts( |
| 313 static_cast<HistogramBase::AtomicCount*>(persistent_counts_.Get())); |
| 314 return true; |
| 315 } |
| 316 |
| 317 HistogramBase::AtomicCount* |
| 318 PersistentSampleVector::CreateCountsStorageWhileLocked() { |
| 319 void* mem = persistent_counts_.Get(); |
| 320 if (!mem) { |
| 321 // The above shouldn't fail but can if Bad Things(tm) are occurring in the |
| 322 // persistent allocator. Crashing isn't a good option so instead just |
| 323 // allocate something from the heap and return that. There will be no |
| 324 // sharing or persistence but worse things are already happening. |
| 325 return new HistogramBase::AtomicCount[counts_size()]; |
| 326 } |
| 327 |
| 328 return static_cast<HistogramBase::AtomicCount*>(mem); |
| 329 } |
| 330 |
128 SampleVectorIterator::SampleVectorIterator( | 331 SampleVectorIterator::SampleVectorIterator( |
129 const std::vector<HistogramBase::AtomicCount>* counts, | 332 const std::vector<HistogramBase::AtomicCount>* counts, |
130 const BucketRanges* bucket_ranges) | 333 const BucketRanges* bucket_ranges) |
131 : counts_(&(*counts)[0]), | 334 : counts_(&(*counts)[0]), |
132 counts_size_(counts->size()), | 335 counts_size_(counts->size()), |
133 bucket_ranges_(bucket_ranges), | 336 bucket_ranges_(bucket_ranges), |
134 index_(0) { | 337 index_(0) { |
135 CHECK_GE(bucket_ranges_->bucket_count(), counts_size_); | 338 CHECK_GE(bucket_ranges_->bucket_count(), counts_size_); |
136 SkipEmptyBuckets(); | 339 SkipEmptyBuckets(); |
137 } | 340 } |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
184 return; | 387 return; |
185 | 388 |
186 while (index_ < counts_size_) { | 389 while (index_ < counts_size_) { |
187 if (subtle::NoBarrier_Load(&counts_[index_]) != 0) | 390 if (subtle::NoBarrier_Load(&counts_[index_]) != 0) |
188 return; | 391 return; |
189 index_++; | 392 index_++; |
190 } | 393 } |
191 } | 394 } |
192 | 395 |
193 } // namespace base | 396 } // namespace base |
OLD | NEW |