blob: 5457485211b5a4e2e6dff8e04d993c2c2c580fe3 [file] [log] [blame]
Avi Drissmane4622aa2022-09-08 20:36:061// Copyright 2012 The Chromium Authors
[email protected]2f7d9cd2012-09-22 03:42:122// 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/sample_vector.h"
6
Jean-Philippe Graveld90e05ca2022-11-27 20:29:477#include <ostream>
8
Hans Wennborgc3cffa62020-04-27 10:09:129#include "base/check_op.h"
Peter McNeeley2e1581a2023-12-14 22:57:1510#include "base/compiler_specific.h"
danakjad724a82024-01-25 17:37:4011#include "base/containers/heap_array.h"
Luc Nguyen8084cab52023-04-14 16:20:5612#include "base/debug/crash_logging.h"
Alexei Svitkine2d87fb5a2024-03-04 16:49:3813#include "base/debug/leak_annotations.h"
bcwhitefa8485b2017-05-01 16:43:2514#include "base/lazy_instance.h"
bcwhitefa8485b2017-05-01 16:43:2515#include "base/memory/ptr_util.h"
Tom Sepez7a008da2023-11-07 21:02:3316#include "base/memory/raw_span.h"
bcwhitefa8485b2017-05-01 16:43:2517#include "base/metrics/persistent_memory_allocator.h"
Hans Wennborgc3cffa62020-04-27 10:09:1218#include "base/notreached.h"
asvitkine3f17b1462017-05-03 21:37:3719#include "base/numerics/safe_conversions.h"
Greg Thompsonde48d062024-01-27 12:19:4920#include "base/strings/strcat.h"
21#include "base/strings/string_number_conversions.h"
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:3822#include "base/strings/stringprintf.h"
bcwhitefa8485b2017-05-01 16:43:2523#include "base/synchronization/lock.h"
24#include "base/threading/platform_thread.h"
25
26// This SampleVector makes use of the single-sample embedded in the base
27// HistogramSamples class. If the count is non-zero then there is guaranteed
28// (within the bounds of "eventual consistency") to be no allocated external
29// storage. Once the full counts storage is allocated, the single-sample must
30// be extracted and disabled.
[email protected]2f7d9cd2012-09-22 03:42:1231
[email protected]2f7d9cd2012-09-22 03:42:1232namespace base {
33
34typedef HistogramBase::Count Count;
35typedef HistogramBase::Sample Sample;
36
Luc Nguyen60b27cb2023-03-29 15:22:5637namespace {
38
39// An iterator for sample vectors.
Luc Nguyen6458a5c2023-03-31 20:59:1740template <typename T>
41class IteratorTemplate : public SampleCountIterator {
Luc Nguyen60b27cb2023-03-29 15:22:5642 public:
Tom Sepez7a008da2023-11-07 21:02:3343 IteratorTemplate(base::span<T> counts, const BucketRanges* bucket_ranges)
44 : counts_(counts), bucket_ranges_(bucket_ranges) {
Luc Nguyen60b27cb2023-03-29 15:22:5645 SkipEmptyBuckets();
46 }
47
Luc Nguyen6458a5c2023-03-31 20:59:1748 ~IteratorTemplate() override;
Luc Nguyen60b27cb2023-03-29 15:22:5649
50 // SampleCountIterator:
Tom Sepez7a008da2023-11-07 21:02:3351 bool Done() const override { return index_ >= counts_.size(); }
Luc Nguyen60b27cb2023-03-29 15:22:5652 void Next() override {
53 DCHECK(!Done());
54 index_++;
55 SkipEmptyBuckets();
56 }
57 void Get(HistogramBase::Sample* min,
58 int64_t* max,
Luc Nguyen6458a5c2023-03-31 20:59:1759 HistogramBase::Count* count) override;
Luc Nguyen60b27cb2023-03-29 15:22:5660
61 // SampleVector uses predefined buckets, so iterator can return bucket index.
62 bool GetBucketIndex(size_t* index) const override {
63 DCHECK(!Done());
64 if (index != nullptr) {
65 *index = index_;
66 }
67 return true;
68 }
69
70 private:
71 void SkipEmptyBuckets() {
72 if (Done()) {
73 return;
74 }
75
Tom Sepez7a008da2023-11-07 21:02:3376 while (index_ < counts_.size()) {
Luc Nguyen60b27cb2023-03-29 15:22:5677 if (subtle::NoBarrier_Load(&counts_[index_]) != 0) {
78 return;
79 }
80 index_++;
81 }
82 }
83
Tom Sepez7a008da2023-11-07 21:02:3384 raw_span<T> counts_;
Luc Nguyen60b27cb2023-03-29 15:22:5685 raw_ptr<const BucketRanges> bucket_ranges_;
Luc Nguyen60b27cb2023-03-29 15:22:5686 size_t index_ = 0;
87};
88
Tom Sepez7a008da2023-11-07 21:02:3389using SampleVectorIterator = IteratorTemplate<const HistogramBase::AtomicCount>;
Luc Nguyen6458a5c2023-03-31 20:59:1790
91template <>
92SampleVectorIterator::~IteratorTemplate() = default;
93
94// Get() for an iterator of a SampleVector.
95template <>
96void SampleVectorIterator::Get(HistogramBase::Sample* min,
97 int64_t* max,
98 HistogramBase::Count* count) {
99 DCHECK(!Done());
100 *min = bucket_ranges_->range(index_);
101 *max = strict_cast<int64_t>(bucket_ranges_->range(index_ + 1));
102 *count = subtle::NoBarrier_Load(&counts_[index_]);
103}
104
Tom Sepez7a008da2023-11-07 21:02:33105using ExtractingSampleVectorIterator =
106 IteratorTemplate<HistogramBase::AtomicCount>;
Luc Nguyen6458a5c2023-03-31 20:59:17107
108template <>
109ExtractingSampleVectorIterator::~IteratorTemplate() {
110 // Ensure that the user has consumed all the samples in order to ensure no
111 // samples are lost.
112 DCHECK(Done());
113}
114
115// Get() for an extracting iterator of a SampleVector.
116template <>
117void ExtractingSampleVectorIterator::Get(HistogramBase::Sample* min,
118 int64_t* max,
119 HistogramBase::Count* count) {
120 DCHECK(!Done());
121 *min = bucket_ranges_->range(index_);
122 *max = strict_cast<int64_t>(bucket_ranges_->range(index_ + 1));
123 *count = subtle::NoBarrier_AtomicExchange(&counts_[index_], 0);
124}
125
Luc Nguyen60b27cb2023-03-29 15:22:56126} // namespace
127
bcwhitefa8485b2017-05-01 16:43:25128SampleVectorBase::SampleVectorBase(uint64_t id,
bcwhitefa8485b2017-05-01 16:43:25129 Metadata* meta,
130 const BucketRanges* bucket_ranges)
danakjad724a82024-01-25 17:37:40131 : HistogramSamples(id, meta),
132 bucket_ranges_(bucket_ranges),
133 counts_size_(bucket_ranges_->bucket_count()) {
134 CHECK_GE(counts_size_, 1u);
bcwhiteb036e4322015-12-10 18:36:34135}
136
Arthur Sonzognibc8777c302022-05-24 09:23:06137SampleVectorBase::SampleVectorBase(uint64_t id,
138 std::unique_ptr<Metadata> meta,
139 const BucketRanges* bucket_ranges)
danakjad724a82024-01-25 17:37:40140 : HistogramSamples(id, std::move(meta)),
141 bucket_ranges_(bucket_ranges),
142 counts_size_(bucket_ranges_->bucket_count()) {
143 CHECK_GE(counts_size_, 1u);
Arthur Sonzognibc8777c302022-05-24 09:23:06144}
145
Chris Watkinsbb7211c2017-11-29 07:16:38146SampleVectorBase::~SampleVectorBase() = default;
[email protected]2f7d9cd2012-09-22 03:42:12147
bcwhitefa8485b2017-05-01 16:43:25148void SampleVectorBase::Accumulate(Sample value, Count count) {
149 const size_t bucket_index = GetBucketIndex(value);
[email protected]2f7d9cd2012-09-22 03:42:12150
bcwhitefa8485b2017-05-01 16:43:25151 // Handle the single-sample case.
danakjad724a82024-01-25 17:37:40152 if (!counts().has_value()) {
bcwhitefa8485b2017-05-01 16:43:25153 // Try to accumulate the parameters into the single-count entry.
154 if (AccumulateSingleSample(value, count, bucket_index)) {
155 // A race condition could lead to a new single-sample being accumulated
156 // above just after another thread executed the MountCountsStorage below.
157 // Since it is mounted, it could be mounted elsewhere and have values
158 // written to it. It's not allowed to have both a single-sample and
159 // entries in the counts array so move the single-sample.
danakjad724a82024-01-25 17:37:40160 if (counts().has_value()) {
bcwhitefa8485b2017-05-01 16:43:25161 MoveSingleSampleToCounts();
danakjad724a82024-01-25 17:37:40162 }
bcwhitefa8485b2017-05-01 16:43:25163 return;
164 }
[email protected]2f7d9cd2012-09-22 03:42:12165
bcwhitefa8485b2017-05-01 16:43:25166 // Need real storage to store both what was in the single-sample plus the
167 // parameter information.
168 MountCountsStorageAndMoveSingleSample();
[email protected]2f7d9cd2012-09-22 03:42:12169 }
bcwhitefa8485b2017-05-01 16:43:25170
171 // Handle the multi-sample case.
Peter McNeeley2e1581a2023-12-14 22:57:15172 Count new_bucket_count =
danakjad724a82024-01-25 17:37:40173 subtle::NoBarrier_AtomicIncrement(&counts_at(bucket_index), count);
asvitkine3f17b1462017-05-03 21:37:37174 IncreaseSumAndCount(strict_cast<int64_t>(count) * value, count);
Brian White537e41a2017-11-01 03:02:13175
176 // TODO(bcwhite) Remove after crbug.com/682680.
Peter McNeeley2e1581a2023-12-14 22:57:15177 Count old_bucket_count = new_bucket_count - count;
178 bool record_negative_sample =
179 (new_bucket_count >= 0) != (old_bucket_count >= 0) && count > 0;
180 if (UNLIKELY(record_negative_sample)) {
Brian White537e41a2017-11-01 03:02:13181 RecordNegativeSample(SAMPLES_ACCUMULATE_OVERFLOW, count);
Peter McNeeley2e1581a2023-12-14 22:57:15182 }
[email protected]2f7d9cd2012-09-22 03:42:12183}
184
bcwhitefa8485b2017-05-01 16:43:25185Count SampleVectorBase::GetCount(Sample value) const {
186 return GetCountAtIndex(GetBucketIndex(value));
[email protected]2f7d9cd2012-09-22 03:42:12187}
188
bcwhitefa8485b2017-05-01 16:43:25189Count SampleVectorBase::TotalCount() const {
190 // Handle the single-sample case.
191 SingleSample sample = single_sample().Load();
danakjad724a82024-01-25 17:37:40192 if (sample.count != 0) {
bcwhitefa8485b2017-05-01 16:43:25193 return sample.count;
danakjad724a82024-01-25 17:37:40194 }
bcwhitefa8485b2017-05-01 16:43:25195
196 // Handle the multi-sample case.
danakjad724a82024-01-25 17:37:40197 if (counts().has_value() || MountExistingCountsStorage()) {
bcwhitefa8485b2017-05-01 16:43:25198 Count count = 0;
danakjad724a82024-01-25 17:37:40199 // TODO(danakj): In C++23 we can skip the `counts_span` lvalue and iterate
200 // over `counts().value()` directly without creating a dangling reference.
201 span<const HistogramBase::AtomicCount> counts_span = counts().value();
202 for (const HistogramBase::AtomicCount& c : counts_span) {
203 count += subtle::NoBarrier_Load(&c);
bcwhitefa8485b2017-05-01 16:43:25204 }
205 return count;
206 }
207
208 // And the no-value case.
209 return 0;
[email protected]2f7d9cd2012-09-22 03:42:12210}
211
bcwhitefa8485b2017-05-01 16:43:25212Count SampleVectorBase::GetCountAtIndex(size_t bucket_index) const {
213 DCHECK(bucket_index < counts_size());
214
215 // Handle the single-sample case.
216 SingleSample sample = single_sample().Load();
danakjad724a82024-01-25 17:37:40217 if (sample.count != 0) {
bcwhitefa8485b2017-05-01 16:43:25218 return sample.bucket == bucket_index ? sample.count : 0;
danakjad724a82024-01-25 17:37:40219 }
bcwhitefa8485b2017-05-01 16:43:25220
221 // Handle the multi-sample case.
danakjad724a82024-01-25 17:37:40222 if (counts().has_value() || MountExistingCountsStorage()) {
223 return subtle::NoBarrier_Load(&counts_at(bucket_index));
224 }
bcwhitefa8485b2017-05-01 16:43:25225
226 // And the no-value case.
227 return 0;
228}
229
230std::unique_ptr<SampleCountIterator> SampleVectorBase::Iterator() const {
231 // Handle the single-sample case.
232 SingleSample sample = single_sample().Load();
233 if (sample.count != 0) {
Luc Nguyen415ee2cf2024-03-07 17:47:10234 static_assert(std::is_unsigned<decltype(SingleSample::bucket)>::value);
235 if (sample.bucket >= bucket_ranges_->bucket_count()) {
236 // Return an empty iterator if the specified bucket is invalid (e.g. due
237 // to corruption). If a different sample is eventually emitted, we will
238 // move from SingleSample to a counts storage, and that time, we will
239 // discard this invalid sample (see MoveSingleSampleToCounts()).
240 return std::make_unique<SampleVectorIterator>(
241 base::span<const HistogramBase::AtomicCount>(), bucket_ranges_);
242 }
243
Jeremy Roman9532f252017-08-16 23:27:24244 return std::make_unique<SingleSampleIterator>(
bcwhitefa8485b2017-05-01 16:43:25245 bucket_ranges_->range(sample.bucket),
Luc Nguyen6458a5c2023-03-31 20:59:17246 bucket_ranges_->range(sample.bucket + 1), sample.count, sample.bucket,
247 /*value_was_extracted=*/false);
bcwhitefa8485b2017-05-01 16:43:25248 }
249
250 // Handle the multi-sample case.
danakjad724a82024-01-25 17:37:40251 if (counts().has_value() || MountExistingCountsStorage()) {
252 return std::make_unique<SampleVectorIterator>(*counts(), bucket_ranges_);
bcwhitefa8485b2017-05-01 16:43:25253 }
254
255 // And the no-value case.
Tom Sepez7a008da2023-11-07 21:02:33256 return std::make_unique<SampleVectorIterator>(
257 base::span<const HistogramBase::AtomicCount>(), bucket_ranges_);
bcwhitefa8485b2017-05-01 16:43:25258}
259
Luc Nguyen6458a5c2023-03-31 20:59:17260std::unique_ptr<SampleCountIterator> SampleVectorBase::ExtractingIterator() {
261 // Handle the single-sample case.
262 SingleSample sample = single_sample().Extract();
263 if (sample.count != 0) {
Luc Nguyen415ee2cf2024-03-07 17:47:10264 static_assert(std::is_unsigned<decltype(SingleSample::bucket)>::value);
265 if (sample.bucket >= bucket_ranges_->bucket_count()) {
266 // Return an empty iterator if the specified bucket is invalid (e.g. due
267 // to corruption). Note that we've already removed the sample from the
268 // underlying data, so this invalid sample is discarded.
269 return std::make_unique<ExtractingSampleVectorIterator>(
270 base::span<HistogramBase::AtomicCount>(), bucket_ranges_);
271 }
272
Luc Nguyen6458a5c2023-03-31 20:59:17273 // Note that we have already extracted the samples (i.e., reset the
274 // underlying data back to 0 samples), even before the iterator has been
275 // used. This means that the caller needs to ensure that this value is
276 // eventually consumed, otherwise the sample is lost. There is no iterator
277 // that simply points to the underlying SingleSample and extracts its value
278 // on-demand because there are tricky edge cases when the SingleSample is
279 // disabled between the creation of the iterator and the actual call to
280 // Get() (for example, due to histogram changing to use a vector to store
281 // its samples).
282 return std::make_unique<SingleSampleIterator>(
283 bucket_ranges_->range(sample.bucket),
284 bucket_ranges_->range(sample.bucket + 1), sample.count, sample.bucket,
285 /*value_was_extracted=*/true);
286 }
287
288 // Handle the multi-sample case.
danakjad724a82024-01-25 17:37:40289 if (counts().has_value() || MountExistingCountsStorage()) {
290 return std::make_unique<ExtractingSampleVectorIterator>(*counts(),
291 bucket_ranges_);
Luc Nguyen6458a5c2023-03-31 20:59:17292 }
293
294 // And the no-value case.
Tom Sepez7a008da2023-11-07 21:02:33295 return std::make_unique<ExtractingSampleVectorIterator>(
296 base::span<HistogramBase::AtomicCount>(), bucket_ranges_);
Luc Nguyen6458a5c2023-03-31 20:59:17297}
298
bcwhitefa8485b2017-05-01 16:43:25299bool SampleVectorBase::AddSubtractImpl(SampleCountIterator* iter,
300 HistogramSamples::Operator op) {
301 // Stop now if there's nothing to do.
danakjad724a82024-01-25 17:37:40302 if (iter->Done()) {
bcwhitefa8485b2017-05-01 16:43:25303 return true;
danakjad724a82024-01-25 17:37:40304 }
bcwhitefa8485b2017-05-01 16:43:25305
306 // Get the first value and its index.
[email protected]2f7d9cd2012-09-22 03:42:12307 HistogramBase::Sample min;
asvitkine3f17b1462017-05-03 21:37:37308 int64_t max;
[email protected]2f7d9cd2012-09-22 03:42:12309 HistogramBase::Count count;
bcwhitefa8485b2017-05-01 16:43:25310 iter->Get(&min, &max, &count);
311 size_t dest_index = GetBucketIndex(min);
[email protected]2f7d9cd2012-09-22 03:42:12312
bcwhitefa8485b2017-05-01 16:43:25313 // The destination must be a superset of the source meaning that though the
314 // incoming ranges will find an exact match, the incoming bucket-index, if
315 // it exists, may be offset from the destination bucket-index. Calculate
316 // that offset of the passed iterator; there are are no overflow checks
317 // because 2's compliment math will work it out in the end.
318 //
319 // Because GetBucketIndex() always returns the same true or false result for
320 // a given iterator object, |index_offset| is either set here and used below,
321 // or never set and never used. The compiler doesn't know this, though, which
322 // is why it's necessary to initialize it to something.
323 size_t index_offset = 0;
324 size_t iter_index;
danakjad724a82024-01-25 17:37:40325 if (iter->GetBucketIndex(&iter_index)) {
bcwhitefa8485b2017-05-01 16:43:25326 index_offset = dest_index - iter_index;
danakjad724a82024-01-25 17:37:40327 }
328 if (dest_index >= counts_size()) {
bcwhitefa8485b2017-05-01 16:43:25329 return false;
danakjad724a82024-01-25 17:37:40330 }
bcwhitefa8485b2017-05-01 16:43:25331
332 // Post-increment. Information about the current sample is not available
333 // after this point.
334 iter->Next();
335
336 // Single-value storage is possible if there is no counts storage and the
337 // retrieved entry is the only one in the iterator.
danakjad724a82024-01-25 17:37:40338 if (!counts().has_value()) {
bcwhitefa8485b2017-05-01 16:43:25339 if (iter->Done()) {
340 // Don't call AccumulateSingleSample because that updates sum and count
341 // which was already done by the caller of this method.
342 if (single_sample().Accumulate(
343 dest_index, op == HistogramSamples::ADD ? count : -count)) {
344 // Handle race-condition that mounted counts storage between above and
345 // here.
danakjad724a82024-01-25 17:37:40346 if (counts().has_value()) {
bcwhitefa8485b2017-05-01 16:43:25347 MoveSingleSampleToCounts();
danakjad724a82024-01-25 17:37:40348 }
bcwhitefa8485b2017-05-01 16:43:25349 return true;
350 }
tdanderson94191022017-04-21 23:19:50351 }
bcwhitefa8485b2017-05-01 16:43:25352
353 // The counts storage will be needed to hold the multiple incoming values.
354 MountCountsStorageAndMoveSingleSample();
bcwhite4162baf2017-04-21 18:58:00355 }
tdanderson94191022017-04-21 23:19:50356
bcwhitefa8485b2017-05-01 16:43:25357 // Go through the iterator and add the counts into correct bucket.
358 while (true) {
359 // Ensure that the sample's min/max match the ranges min/max.
360 if (min != bucket_ranges_->range(dest_index) ||
361 max != bucket_ranges_->range(dest_index + 1)) {
Luc Nguyen8084cab52023-04-14 16:20:56362#if !BUILDFLAG(IS_NACL)
363 // TODO(crbug/1432981): Remove these. They are used to investigate
364 // unexpected failures.
365 SCOPED_CRASH_KEY_NUMBER("SampleVector", "min", min);
366 SCOPED_CRASH_KEY_NUMBER("SampleVector", "max", max);
367 SCOPED_CRASH_KEY_NUMBER("SampleVector", "range_min",
368 bucket_ranges_->range(dest_index));
369 SCOPED_CRASH_KEY_NUMBER("SampleVector", "range_max",
370 bucket_ranges_->range(dest_index + 1));
371#endif // !BUILDFLAG(IS_NACL)
bcwhitefa8485b2017-05-01 16:43:25372 NOTREACHED() << "sample=" << min << "," << max
373 << "; range=" << bucket_ranges_->range(dest_index) << ","
374 << bucket_ranges_->range(dest_index + 1);
375 return false;
376 }
377
378 // Sample's bucket matches exactly. Adjust count.
379 subtle::NoBarrier_AtomicIncrement(
danakjad724a82024-01-25 17:37:40380 &counts_at(dest_index), op == HistogramSamples::ADD ? count : -count);
bcwhitefa8485b2017-05-01 16:43:25381
382 // Advance to the next iterable sample. See comments above for how
383 // everything works.
danakjad724a82024-01-25 17:37:40384 if (iter->Done()) {
bcwhitefa8485b2017-05-01 16:43:25385 return true;
danakjad724a82024-01-25 17:37:40386 }
bcwhitefa8485b2017-05-01 16:43:25387 iter->Get(&min, &max, &count);
388 if (iter->GetBucketIndex(&iter_index)) {
389 // Destination bucket is a known offset from the source bucket.
390 dest_index = iter_index + index_offset;
391 } else {
392 // Destination bucket has to be determined anew each time.
393 dest_index = GetBucketIndex(min);
394 }
danakjad724a82024-01-25 17:37:40395 if (dest_index >= counts_size()) {
bcwhitefa8485b2017-05-01 16:43:25396 return false;
danakjad724a82024-01-25 17:37:40397 }
bcwhitefa8485b2017-05-01 16:43:25398 iter->Next();
399 }
[email protected]2f7d9cd2012-09-22 03:42:12400}
401
Weilun Shibf3ceb192020-07-31 22:39:37402// Uses simple binary search or calculates the index directly if it's an "exact"
403// linear histogram. This is very general, but there are better approaches if we
404// knew that the buckets were linearly distributed.
bcwhitefa8485b2017-05-01 16:43:25405size_t SampleVectorBase::GetBucketIndex(Sample value) const {
[email protected]15ce3842013-06-27 14:38:45406 size_t bucket_count = bucket_ranges_->bucket_count();
[email protected]2f7d9cd2012-09-22 03:42:12407 CHECK_GE(value, bucket_ranges_->range(0));
408 CHECK_LT(value, bucket_ranges_->range(bucket_count));
409
Weilun Shibf3ceb192020-07-31 22:39:37410 // For "exact" linear histograms, e.g. bucket_count = maximum + 1, their
411 // minimum is 1 and bucket sizes are 1. Thus, we don't need to binary search
412 // the bucket index. The bucket index for bucket |value| is just the |value|.
413 Sample maximum = bucket_ranges_->range(bucket_count - 1);
414 if (maximum == static_cast<Sample>(bucket_count - 1)) {
415 // |value| is in the underflow bucket.
danakjad724a82024-01-25 17:37:40416 if (value < 1) {
Weilun Shibf3ceb192020-07-31 22:39:37417 return 0;
danakjad724a82024-01-25 17:37:40418 }
Weilun Shibf3ceb192020-07-31 22:39:37419 // |value| is in the overflow bucket.
danakjad724a82024-01-25 17:37:40420 if (value > maximum) {
Weilun Shibf3ceb192020-07-31 22:39:37421 return bucket_count - 1;
danakjad724a82024-01-25 17:37:40422 }
Weilun Shibf3ceb192020-07-31 22:39:37423 return static_cast<size_t>(value);
424 }
425
[email protected]2f7d9cd2012-09-22 03:42:12426 size_t under = 0;
427 size_t over = bucket_count;
428 size_t mid;
429 do {
430 DCHECK_GE(over, under);
danakjad724a82024-01-25 17:37:40431 mid = under + (over - under) / 2;
432 if (mid == under) {
[email protected]2f7d9cd2012-09-22 03:42:12433 break;
danakjad724a82024-01-25 17:37:40434 }
435 if (bucket_ranges_->range(mid) <= value) {
[email protected]2f7d9cd2012-09-22 03:42:12436 under = mid;
danakjad724a82024-01-25 17:37:40437 } else {
[email protected]2f7d9cd2012-09-22 03:42:12438 over = mid;
danakjad724a82024-01-25 17:37:40439 }
[email protected]2f7d9cd2012-09-22 03:42:12440 } while (true);
441
442 DCHECK_LE(bucket_ranges_->range(mid), value);
443 CHECK_GT(bucket_ranges_->range(mid + 1), value);
444 return mid;
445}
446
bcwhitefa8485b2017-05-01 16:43:25447void SampleVectorBase::MoveSingleSampleToCounts() {
danakjad724a82024-01-25 17:37:40448 DCHECK(counts().has_value());
bcwhitefa8485b2017-05-01 16:43:25449
450 // Disable the single-sample since there is now counts storage for the data.
Luc Nguyen5f4f42c2023-03-24 23:29:07451 SingleSample sample = single_sample().ExtractAndDisable();
bcwhitefa8485b2017-05-01 16:43:25452
453 // Stop here if there is no "count" as trying to find the bucket index of
454 // an invalid (including zero) "value" will crash.
danakjad724a82024-01-25 17:37:40455 if (sample.count == 0) {
bcwhitefa8485b2017-05-01 16:43:25456 return;
danakjad724a82024-01-25 17:37:40457 }
bcwhitefa8485b2017-05-01 16:43:25458
Will Harris552939b2023-02-17 21:09:35459 // Stop here if the sample bucket would be out of range for the AtomicCount
460 // array.
461 if (sample.bucket >= counts_size()) {
462 return;
463 }
464
bcwhitefa8485b2017-05-01 16:43:25465 // Move the value into storage. Sum and redundant-count already account
466 // for this entry so no need to call IncreaseSumAndCount().
danakjad724a82024-01-25 17:37:40467 subtle::NoBarrier_AtomicIncrement(&counts_at(sample.bucket), sample.count);
bcwhitefa8485b2017-05-01 16:43:25468}
469
470void SampleVectorBase::MountCountsStorageAndMoveSingleSample() {
471 // There are many SampleVector objects and the lock is needed very
472 // infrequently (just when advancing from single-sample to multi-sample) so
473 // define a single, global lock that all can use. This lock only prevents
danakjad724a82024-01-25 17:37:40474 // concurrent entry into the code below; access and updates to |counts_data_|
bcwhitefa8485b2017-05-01 16:43:25475 // still requires atomic operations.
476 static LazyInstance<Lock>::Leaky counts_lock = LAZY_INSTANCE_INITIALIZER;
danakjad724a82024-01-25 17:37:40477 if (counts_data_.load(std::memory_order_relaxed) == nullptr) {
bcwhitefa8485b2017-05-01 16:43:25478 AutoLock lock(counts_lock.Get());
danakjad724a82024-01-25 17:37:40479 if (counts_data_.load(std::memory_order_relaxed) == nullptr) {
bcwhitefa8485b2017-05-01 16:43:25480 // Create the actual counts storage while the above lock is acquired.
danakjad724a82024-01-25 17:37:40481 span<HistogramBase::Count> counts = CreateCountsStorageWhileLocked();
482 // Point |counts()| to the newly created storage. This is done while
bcwhitefa8485b2017-05-01 16:43:25483 // locked to prevent possible concurrent calls to CreateCountsStorage
484 // but, between that call and here, other threads could notice the
dschuyler11857732017-06-09 20:45:33485 // existence of the storage and race with this to set_counts(). That's
bcwhitefa8485b2017-05-01 16:43:25486 // okay because (a) it's atomic and (b) it always writes the same value.
487 set_counts(counts);
488 }
489 }
490
491 // Move any single-sample into the newly mounted storage.
492 MoveSingleSampleToCounts();
493}
494
495SampleVector::SampleVector(const BucketRanges* bucket_ranges)
496 : SampleVector(0, bucket_ranges) {}
497
498SampleVector::SampleVector(uint64_t id, const BucketRanges* bucket_ranges)
Arthur Sonzognibc8777c302022-05-24 09:23:06499 : SampleVectorBase(id, std::make_unique<LocalMetadata>(), bucket_ranges) {}
bcwhitefa8485b2017-05-01 16:43:25500
Arthur Sonzognibc8777c302022-05-24 09:23:06501SampleVector::~SampleVector() = default;
bcwhitefa8485b2017-05-01 16:43:25502
Luc Nguyenbbaab292023-09-28 23:30:57503bool SampleVector::IsDefinitelyEmpty() const {
504 // If we are still using SingleSample, and it has a count of 0, then |this|
505 // has no samples. If we are not using SingleSample, always return false, even
506 // though it is possible that |this| has no samples (e.g. we are using a
507 // counts array and all the bucket counts are 0). If we are wrong, this will
508 // just make the caller perform some extra work thinking that |this| is
509 // non-empty.
510 AtomicSingleSample sample = single_sample();
511 return HistogramSamples::IsDefinitelyEmpty() && !sample.IsDisabled() &&
512 sample.Load().count == 0;
513}
514
bcwhitefa8485b2017-05-01 16:43:25515bool SampleVector::MountExistingCountsStorage() const {
516 // There is never any existing storage other than what is already in use.
danakjad724a82024-01-25 17:37:40517 return counts().has_value();
bcwhitefa8485b2017-05-01 16:43:25518}
519
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38520std::string SampleVector::GetAsciiHeader(StringPiece histogram_name,
521 int32_t flags) const {
522 Count sample_count = TotalCount();
523 std::string output;
Greg Thompsonde48d062024-01-27 12:19:49524 StrAppend(&output, {"Histogram: ", histogram_name, " recorded ",
525 NumberToString(sample_count), " samples"});
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38526 if (sample_count == 0) {
527 DCHECK_EQ(sum(), 0);
528 } else {
529 double mean = static_cast<float>(sum()) / sample_count;
530 StringAppendF(&output, ", mean = %.1f", mean);
531 }
danakjad724a82024-01-25 17:37:40532 if (flags) {
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38533 StringAppendF(&output, " (flags = 0x%x)", flags);
danakjad724a82024-01-25 17:37:40534 }
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38535 return output;
536}
537
538std::string SampleVector::GetAsciiBody() const {
539 Count sample_count = TotalCount();
540
541 // Prepare to normalize graphical rendering of bucket contents.
542 double max_size = 0;
543 double scaling_factor = 1;
544 max_size = GetPeakBucketSize();
545 // Scale histogram bucket counts to take at most 72 characters.
546 // Note: Keep in sync w/ kLineLength histogram_samples.cc
547 const double kLineLength = 72;
danakjad724a82024-01-25 17:37:40548 if (max_size > kLineLength) {
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38549 scaling_factor = kLineLength / max_size;
danakjad724a82024-01-25 17:37:40550 }
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38551
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38552 // Calculate largest print width needed for any of our bucket range displays.
553 size_t print_width = 1;
554 for (uint32_t i = 0; i < bucket_count(); ++i) {
555 if (GetCountAtIndex(i)) {
556 size_t width =
557 GetSimpleAsciiBucketRange(bucket_ranges()->range(i)).size() + 1;
danakjad724a82024-01-25 17:37:40558 if (width > print_width) {
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38559 print_width = width;
danakjad724a82024-01-25 17:37:40560 }
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38561 }
562 }
563
564 int64_t remaining = sample_count;
565 int64_t past = 0;
566 std::string output;
567 // Output the actual histogram graph.
568 for (uint32_t i = 0; i < bucket_count(); ++i) {
569 Count current = GetCountAtIndex(i);
570 remaining -= current;
571 std::string range = GetSimpleAsciiBucketRange(bucket_ranges()->range(i));
572 output.append(range);
danakjad724a82024-01-25 17:37:40573 for (size_t j = 0; range.size() + j < print_width + 1; ++j) {
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38574 output.push_back(' ');
danakjad724a82024-01-25 17:37:40575 }
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38576 if (0 == current && i < bucket_count() - 1 && 0 == GetCountAtIndex(i + 1)) {
577 while (i < bucket_count() - 1 && 0 == GetCountAtIndex(i + 1)) {
578 ++i;
579 }
580 output.append("... \n");
581 continue; // No reason to plot emptiness.
582 }
583 Count current_size = round(current * scaling_factor);
584 WriteAsciiBucketGraph(current_size, kLineLength, &output);
585 WriteAsciiBucketContext(past, current, remaining, i, &output);
586 output.append("\n");
587 past += current;
588 }
589 DCHECK_EQ(sample_count, past);
590 return output;
591}
592
593double SampleVector::GetPeakBucketSize() const {
594 Count max = 0;
595 for (uint32_t i = 0; i < bucket_count(); ++i) {
596 Count current = GetCountAtIndex(i);
danakjad724a82024-01-25 17:37:40597 if (current > max) {
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38598 max = current;
danakjad724a82024-01-25 17:37:40599 }
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38600 }
601 return max;
602}
603
604void SampleVector::WriteAsciiBucketContext(int64_t past,
605 Count current,
606 int64_t remaining,
607 uint32_t current_bucket_index,
608 std::string* output) const {
609 double scaled_sum = (past + current + remaining) / 100.0;
610 WriteAsciiBucketValue(current, scaled_sum, output);
611 if (0 < current_bucket_index) {
612 double percentage = past / scaled_sum;
613 StringAppendF(output, " {%3.1f%%}", percentage);
614 }
615}
616
danakjad724a82024-01-25 17:37:40617span<HistogramBase::AtomicCount>
618SampleVector::CreateCountsStorageWhileLocked() {
bcwhitefa8485b2017-05-01 16:43:25619 local_counts_.resize(counts_size());
danakjad724a82024-01-25 17:37:40620 return local_counts_;
bcwhitefa8485b2017-05-01 16:43:25621}
622
623PersistentSampleVector::PersistentSampleVector(
624 uint64_t id,
625 const BucketRanges* bucket_ranges,
626 Metadata* meta,
627 const DelayedPersistentAllocation& counts)
628 : SampleVectorBase(id, meta, bucket_ranges), persistent_counts_(counts) {
629 // Only mount the full storage if the single-sample has been disabled.
630 // Otherwise, it is possible for this object instance to start using (empty)
631 // storage that was created incidentally while another instance continues to
632 // update to the single sample. This "incidental creation" can happen because
633 // the memory is a DelayedPersistentAllocation which allows multiple memory
634 // blocks within it and applies an all-or-nothing approach to the allocation.
635 // Thus, a request elsewhere for one of the _other_ blocks would make _this_
636 // block available even though nothing has explicitly requested it.
637 //
638 // Note that it's not possible for the ctor to mount existing storage and
639 // move any single-sample to it because sometimes the persistent memory is
640 // read-only. Only non-const methods (which assume that memory is read/write)
641 // can do that.
642 if (single_sample().IsDisabled()) {
643 bool success = MountExistingCountsStorage();
644 DCHECK(success);
645 }
646}
647
Chris Watkinsbb7211c2017-11-29 07:16:38648PersistentSampleVector::~PersistentSampleVector() = default;
bcwhitefa8485b2017-05-01 16:43:25649
Luc Nguyenbbaab292023-09-28 23:30:57650bool PersistentSampleVector::IsDefinitelyEmpty() const {
651 // Not implemented.
652 NOTREACHED();
653
654 // Always return false. If we are wrong, this will just make the caller
655 // perform some extra work thinking that |this| is non-empty.
656 return false;
657}
658
bcwhitefa8485b2017-05-01 16:43:25659bool PersistentSampleVector::MountExistingCountsStorage() const {
660 // There is no early exit if counts is not yet mounted because, given that
661 // this is a virtual function, it's more efficient to do that at the call-
662 // site. There is no danger, however, should this get called anyway (perhaps
danakjad724a82024-01-25 17:37:40663 // because of a race condition) because at worst the `counts_data_` and
664 // `counts_size_` members would be over-written (in an atomic manner)
665 // with the exact same values.
bcwhitefa8485b2017-05-01 16:43:25666
danakjad724a82024-01-25 17:37:40667 if (!persistent_counts_.reference()) {
bcwhitefa8485b2017-05-01 16:43:25668 return false; // Nothing to mount.
bcwhitefa8485b2017-05-01 16:43:25669 }
670
danakjad724a82024-01-25 17:37:40671 // Mount the counts array in position. This shouldn't fail but can if the
672 // data is corrupt or incomplete.
673 span<HistogramBase::AtomicCount> mem =
674 persistent_counts_.Get<HistogramBase::AtomicCount>();
675 if (mem.empty()) {
676 return false;
677 }
678 // Uses a span that only covers the counts the SampleVector should have
679 // access to, which can be a subset of the entire persistent allocation.
680 set_counts(mem.first(counts_size()));
681 return true;
682}
683
684span<HistogramBase::AtomicCount>
685PersistentSampleVector::CreateCountsStorageWhileLocked() {
686 span<HistogramBase::AtomicCount> mem =
687 persistent_counts_.Get<HistogramBase::AtomicCount>();
688 if (mem.empty()) {
689 // The above shouldn't fail but can if Bad Things(tm) are occurring in
690 // the persistent allocator. Crashing isn't a good option so instead
691 // just allocate something from the heap that we will leak and return that.
692 // There will be no sharing or persistence but worse things are already
693 // happening.
694 auto array = HeapArray<HistogramBase::AtomicCount>::WithSize(counts_size());
Alexei Svitkine2d87fb5a2024-03-04 16:49:38695 ANNOTATE_LEAKING_OBJECT_PTR(array.data());
danakjad724a82024-01-25 17:37:40696 return std::move(array).leak();
697 }
698
699 // Returns a span that only covers the counts the SampleVector should have
700 // access to, which can be a subset of the entire persistent allocation.
701 return mem.first(counts_size());
bcwhitefa8485b2017-05-01 16:43:25702}
703
[email protected]2f7d9cd2012-09-22 03:42:12704} // namespace base