blob: 8f95c4ead1b5cf47b4484e58f81b099e653f9e3c [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>
Helmut Januschka274c3802024-03-12 23:31:298#include <string_view>
Peter Kastinga4614032025-01-07 17:51:279#include <type_traits>
Jean-Philippe Graveld90e05ca2022-11-27 20:29:4710
Hans Wennborgc3cffa62020-04-27 10:09:1211#include "base/check_op.h"
Peter McNeeley2e1581a2023-12-14 22:57:1512#include "base/compiler_specific.h"
danakjad724a82024-01-25 17:37:4013#include "base/containers/heap_array.h"
Luc Nguyen8084cab52023-04-14 16:20:5614#include "base/debug/crash_logging.h"
Roger McFarlane81d67692025-04-09 22:33:4115#include "base/debug/dump_without_crashing.h"
Alexei Svitkine2d87fb5a2024-03-04 16:49:3816#include "base/debug/leak_annotations.h"
bcwhitefa8485b2017-05-01 16:43:2517#include "base/lazy_instance.h"
bcwhitefa8485b2017-05-01 16:43:2518#include "base/memory/ptr_util.h"
Tom Sepez7a008da2023-11-07 21:02:3319#include "base/memory/raw_span.h"
Alexei Svitkinee806b982024-09-20 14:49:2220#include "base/metrics/histogram_macros.h"
bcwhitefa8485b2017-05-01 16:43:2521#include "base/metrics/persistent_memory_allocator.h"
Hans Wennborgc3cffa62020-04-27 10:09:1222#include "base/notreached.h"
asvitkine3f17b1462017-05-03 21:37:3723#include "base/numerics/safe_conversions.h"
Greg Thompsonde48d062024-01-27 12:19:4924#include "base/strings/strcat.h"
25#include "base/strings/string_number_conversions.h"
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:3826#include "base/strings/stringprintf.h"
bcwhitefa8485b2017-05-01 16:43:2527#include "base/synchronization/lock.h"
28#include "base/threading/platform_thread.h"
29
30// This SampleVector makes use of the single-sample embedded in the base
31// HistogramSamples class. If the count is non-zero then there is guaranteed
32// (within the bounds of "eventual consistency") to be no allocated external
33// storage. Once the full counts storage is allocated, the single-sample must
34// be extracted and disabled.
[email protected]2f7d9cd2012-09-22 03:42:1235
[email protected]2f7d9cd2012-09-22 03:42:1236namespace base {
37
Ramon Cano Apariciob2cba0f2025-01-22 21:26:1038typedef HistogramBase::Count32 Count32;
Ramon Cano Aparicio79e06642025-01-09 19:10:2239typedef HistogramBase::Sample32 Sample32;
[email protected]2f7d9cd2012-09-22 03:42:1240
Luc Nguyen60b27cb2023-03-29 15:22:5641namespace {
42
43// An iterator for sample vectors.
Peter Kastinga4614032025-01-07 17:51:2744template <bool support_extraction>
45class SampleVectorIterator : public SampleCountIterator {
46 private:
47 using T = std::conditional_t<support_extraction,
48 HistogramBase::AtomicCount,
49 const HistogramBase::AtomicCount>;
50
Luc Nguyen60b27cb2023-03-29 15:22:5651 public:
Peter Kastinga4614032025-01-07 17:51:2752 SampleVectorIterator(base::span<T> counts, const BucketRanges* bucket_ranges)
Tom Sepez7a008da2023-11-07 21:02:3353 : counts_(counts), bucket_ranges_(bucket_ranges) {
Luc Nguyen60b27cb2023-03-29 15:22:5654 SkipEmptyBuckets();
55 }
56
Peter Kastinga4614032025-01-07 17:51:2757 ~SampleVectorIterator() override {
58 if constexpr (support_extraction) {
59 // Ensure that the user has consumed all the samples in order to ensure no
60 // samples are lost.
61 DCHECK(Done());
62 }
63 }
Luc Nguyen60b27cb2023-03-29 15:22:5664
65 // SampleCountIterator:
Tom Sepez7a008da2023-11-07 21:02:3366 bool Done() const override { return index_ >= counts_.size(); }
Luc Nguyen60b27cb2023-03-29 15:22:5667 void Next() override {
68 DCHECK(!Done());
Peter Kastinga4614032025-01-07 17:51:2769 ++index_;
Luc Nguyen60b27cb2023-03-29 15:22:5670 SkipEmptyBuckets();
71 }
Ramon Cano Aparicio79e06642025-01-09 19:10:2272 void Get(Sample32* min,
Luc Nguyen60b27cb2023-03-29 15:22:5673 int64_t* max,
Ramon Cano Apariciob2cba0f2025-01-22 21:26:1074 HistogramBase::Count32* count) override {
Peter Kastinga4614032025-01-07 17:51:2775 DCHECK(!Done());
76 *min = bucket_ranges_->range(index_);
77 *max = strict_cast<int64_t>(bucket_ranges_->range(index_ + 1));
78 if constexpr (support_extraction) {
79 *count = subtle::NoBarrier_AtomicExchange(&counts_[index_], 0);
80 } else {
81 *count = subtle::NoBarrier_Load(&counts_[index_]);
82 }
83 }
Luc Nguyen60b27cb2023-03-29 15:22:5684
85 // SampleVector uses predefined buckets, so iterator can return bucket index.
86 bool GetBucketIndex(size_t* index) const override {
87 DCHECK(!Done());
88 if (index != nullptr) {
89 *index = index_;
90 }
91 return true;
92 }
93
94 private:
95 void SkipEmptyBuckets() {
96 if (Done()) {
97 return;
98 }
99
Tom Sepez7a008da2023-11-07 21:02:33100 while (index_ < counts_.size()) {
Luc Nguyen60b27cb2023-03-29 15:22:56101 if (subtle::NoBarrier_Load(&counts_[index_]) != 0) {
102 return;
103 }
Peter Kastinga4614032025-01-07 17:51:27104 ++index_;
Luc Nguyen60b27cb2023-03-29 15:22:56105 }
106 }
107
Tom Sepez7a008da2023-11-07 21:02:33108 raw_span<T> counts_;
Luc Nguyen60b27cb2023-03-29 15:22:56109 raw_ptr<const BucketRanges> bucket_ranges_;
Luc Nguyen60b27cb2023-03-29 15:22:56110 size_t index_ = 0;
111};
112
113} // namespace
114
bcwhitefa8485b2017-05-01 16:43:25115SampleVectorBase::SampleVectorBase(uint64_t id,
bcwhitefa8485b2017-05-01 16:43:25116 Metadata* meta,
117 const BucketRanges* bucket_ranges)
danakjad724a82024-01-25 17:37:40118 : HistogramSamples(id, meta),
119 bucket_ranges_(bucket_ranges),
120 counts_size_(bucket_ranges_->bucket_count()) {
121 CHECK_GE(counts_size_, 1u);
bcwhiteb036e4322015-12-10 18:36:34122}
123
Arthur Sonzognibc8777c302022-05-24 09:23:06124SampleVectorBase::SampleVectorBase(uint64_t id,
125 std::unique_ptr<Metadata> meta,
126 const BucketRanges* bucket_ranges)
danakjad724a82024-01-25 17:37:40127 : HistogramSamples(id, std::move(meta)),
128 bucket_ranges_(bucket_ranges),
129 counts_size_(bucket_ranges_->bucket_count()) {
130 CHECK_GE(counts_size_, 1u);
Arthur Sonzognibc8777c302022-05-24 09:23:06131}
132
Chris Watkinsbb7211c2017-11-29 07:16:38133SampleVectorBase::~SampleVectorBase() = default;
[email protected]2f7d9cd2012-09-22 03:42:12134
Ramon Cano Apariciob2cba0f2025-01-22 21:26:10135void SampleVectorBase::Accumulate(Sample32 value, Count32 count) {
bcwhitefa8485b2017-05-01 16:43:25136 const size_t bucket_index = GetBucketIndex(value);
[email protected]2f7d9cd2012-09-22 03:42:12137
bcwhitefa8485b2017-05-01 16:43:25138 // Handle the single-sample case.
danakjad724a82024-01-25 17:37:40139 if (!counts().has_value()) {
bcwhitefa8485b2017-05-01 16:43:25140 // Try to accumulate the parameters into the single-count entry.
141 if (AccumulateSingleSample(value, count, bucket_index)) {
142 // A race condition could lead to a new single-sample being accumulated
143 // above just after another thread executed the MountCountsStorage below.
144 // Since it is mounted, it could be mounted elsewhere and have values
145 // written to it. It's not allowed to have both a single-sample and
146 // entries in the counts array so move the single-sample.
danakjad724a82024-01-25 17:37:40147 if (counts().has_value()) {
bcwhitefa8485b2017-05-01 16:43:25148 MoveSingleSampleToCounts();
danakjad724a82024-01-25 17:37:40149 }
bcwhitefa8485b2017-05-01 16:43:25150 return;
151 }
[email protected]2f7d9cd2012-09-22 03:42:12152
bcwhitefa8485b2017-05-01 16:43:25153 // Need real storage to store both what was in the single-sample plus the
154 // parameter information.
155 MountCountsStorageAndMoveSingleSample();
[email protected]2f7d9cd2012-09-22 03:42:12156 }
bcwhitefa8485b2017-05-01 16:43:25157
158 // Handle the multi-sample case.
Ramon Cano Apariciob2cba0f2025-01-22 21:26:10159 Count32 new_bucket_count =
danakjad724a82024-01-25 17:37:40160 subtle::NoBarrier_AtomicIncrement(&counts_at(bucket_index), count);
asvitkine3f17b1462017-05-03 21:37:37161 IncreaseSumAndCount(strict_cast<int64_t>(count) * value, count);
Brian White537e41a2017-11-01 03:02:13162
163 // TODO(bcwhite) Remove after crbug.com/682680.
Ramon Cano Apariciob2cba0f2025-01-22 21:26:10164 Count32 old_bucket_count = new_bucket_count - count;
Peter McNeeley2e1581a2023-12-14 22:57:15165 bool record_negative_sample =
166 (new_bucket_count >= 0) != (old_bucket_count >= 0) && count > 0;
Peter Kastingfa488992024-08-06 07:48:14167 if (record_negative_sample) [[unlikely]] {
Brian White537e41a2017-11-01 03:02:13168 RecordNegativeSample(SAMPLES_ACCUMULATE_OVERFLOW, count);
Peter McNeeley2e1581a2023-12-14 22:57:15169 }
[email protected]2f7d9cd2012-09-22 03:42:12170}
171
Ramon Cano Apariciob2cba0f2025-01-22 21:26:10172Count32 SampleVectorBase::GetCount(Sample32 value) const {
bcwhitefa8485b2017-05-01 16:43:25173 return GetCountAtIndex(GetBucketIndex(value));
[email protected]2f7d9cd2012-09-22 03:42:12174}
175
Ramon Cano Apariciob2cba0f2025-01-22 21:26:10176Count32 SampleVectorBase::TotalCount() const {
bcwhitefa8485b2017-05-01 16:43:25177 // Handle the single-sample case.
178 SingleSample sample = single_sample().Load();
danakjad724a82024-01-25 17:37:40179 if (sample.count != 0) {
bcwhitefa8485b2017-05-01 16:43:25180 return sample.count;
danakjad724a82024-01-25 17:37:40181 }
bcwhitefa8485b2017-05-01 16:43:25182
183 // Handle the multi-sample case.
danakjad724a82024-01-25 17:37:40184 if (counts().has_value() || MountExistingCountsStorage()) {
Ramon Cano Apariciob2cba0f2025-01-22 21:26:10185 Count32 count = 0;
danakjad724a82024-01-25 17:37:40186 // TODO(danakj): In C++23 we can skip the `counts_span` lvalue and iterate
187 // over `counts().value()` directly without creating a dangling reference.
188 span<const HistogramBase::AtomicCount> counts_span = counts().value();
189 for (const HistogramBase::AtomicCount& c : counts_span) {
190 count += subtle::NoBarrier_Load(&c);
bcwhitefa8485b2017-05-01 16:43:25191 }
192 return count;
193 }
194
195 // And the no-value case.
196 return 0;
[email protected]2f7d9cd2012-09-22 03:42:12197}
198
Ramon Cano Apariciob2cba0f2025-01-22 21:26:10199Count32 SampleVectorBase::GetCountAtIndex(size_t bucket_index) const {
bcwhitefa8485b2017-05-01 16:43:25200 DCHECK(bucket_index < counts_size());
201
202 // Handle the single-sample case.
203 SingleSample sample = single_sample().Load();
danakjad724a82024-01-25 17:37:40204 if (sample.count != 0) {
bcwhitefa8485b2017-05-01 16:43:25205 return sample.bucket == bucket_index ? sample.count : 0;
danakjad724a82024-01-25 17:37:40206 }
bcwhitefa8485b2017-05-01 16:43:25207
208 // Handle the multi-sample case.
danakjad724a82024-01-25 17:37:40209 if (counts().has_value() || MountExistingCountsStorage()) {
210 return subtle::NoBarrier_Load(&counts_at(bucket_index));
211 }
bcwhitefa8485b2017-05-01 16:43:25212
213 // And the no-value case.
214 return 0;
215}
216
217std::unique_ptr<SampleCountIterator> SampleVectorBase::Iterator() const {
218 // Handle the single-sample case.
219 SingleSample sample = single_sample().Load();
220 if (sample.count != 0) {
Luc Nguyen415ee2cf2024-03-07 17:47:10221 static_assert(std::is_unsigned<decltype(SingleSample::bucket)>::value);
222 if (sample.bucket >= bucket_ranges_->bucket_count()) {
223 // Return an empty iterator if the specified bucket is invalid (e.g. due
224 // to corruption). If a different sample is eventually emitted, we will
225 // move from SingleSample to a counts storage, and that time, we will
226 // discard this invalid sample (see MoveSingleSampleToCounts()).
Peter Kastinga4614032025-01-07 17:51:27227 return std::make_unique<SampleVectorIterator<false>>(
Luc Nguyen415ee2cf2024-03-07 17:47:10228 base::span<const HistogramBase::AtomicCount>(), bucket_ranges_);
229 }
230
Jeremy Roman9532f252017-08-16 23:27:24231 return std::make_unique<SingleSampleIterator>(
bcwhitefa8485b2017-05-01 16:43:25232 bucket_ranges_->range(sample.bucket),
Luc Nguyen6458a5c2023-03-31 20:59:17233 bucket_ranges_->range(sample.bucket + 1), sample.count, sample.bucket,
234 /*value_was_extracted=*/false);
bcwhitefa8485b2017-05-01 16:43:25235 }
236
237 // Handle the multi-sample case.
danakjad724a82024-01-25 17:37:40238 if (counts().has_value() || MountExistingCountsStorage()) {
Peter Kastinga4614032025-01-07 17:51:27239 return std::make_unique<SampleVectorIterator<false>>(*counts(),
240 bucket_ranges_);
bcwhitefa8485b2017-05-01 16:43:25241 }
242
243 // And the no-value case.
Peter Kastinga4614032025-01-07 17:51:27244 return std::make_unique<SampleVectorIterator<false>>(
Tom Sepez7a008da2023-11-07 21:02:33245 base::span<const HistogramBase::AtomicCount>(), bucket_ranges_);
bcwhitefa8485b2017-05-01 16:43:25246}
247
Luc Nguyen6458a5c2023-03-31 20:59:17248std::unique_ptr<SampleCountIterator> SampleVectorBase::ExtractingIterator() {
249 // Handle the single-sample case.
250 SingleSample sample = single_sample().Extract();
251 if (sample.count != 0) {
Luc Nguyen415ee2cf2024-03-07 17:47:10252 static_assert(std::is_unsigned<decltype(SingleSample::bucket)>::value);
253 if (sample.bucket >= bucket_ranges_->bucket_count()) {
254 // Return an empty iterator if the specified bucket is invalid (e.g. due
255 // to corruption). Note that we've already removed the sample from the
256 // underlying data, so this invalid sample is discarded.
Peter Kastinga4614032025-01-07 17:51:27257 return std::make_unique<SampleVectorIterator<true>>(
Luc Nguyen415ee2cf2024-03-07 17:47:10258 base::span<HistogramBase::AtomicCount>(), bucket_ranges_);
259 }
260
Luc Nguyen6458a5c2023-03-31 20:59:17261 // Note that we have already extracted the samples (i.e., reset the
262 // underlying data back to 0 samples), even before the iterator has been
263 // used. This means that the caller needs to ensure that this value is
264 // eventually consumed, otherwise the sample is lost. There is no iterator
265 // that simply points to the underlying SingleSample and extracts its value
266 // on-demand because there are tricky edge cases when the SingleSample is
267 // disabled between the creation of the iterator and the actual call to
268 // Get() (for example, due to histogram changing to use a vector to store
269 // its samples).
270 return std::make_unique<SingleSampleIterator>(
271 bucket_ranges_->range(sample.bucket),
272 bucket_ranges_->range(sample.bucket + 1), sample.count, sample.bucket,
273 /*value_was_extracted=*/true);
274 }
275
276 // Handle the multi-sample case.
danakjad724a82024-01-25 17:37:40277 if (counts().has_value() || MountExistingCountsStorage()) {
Peter Kastinga4614032025-01-07 17:51:27278 return std::make_unique<SampleVectorIterator<true>>(*counts(),
279 bucket_ranges_);
Luc Nguyen6458a5c2023-03-31 20:59:17280 }
281
282 // And the no-value case.
Peter Kastinga4614032025-01-07 17:51:27283 return std::make_unique<SampleVectorIterator<true>>(
Tom Sepez7a008da2023-11-07 21:02:33284 base::span<HistogramBase::AtomicCount>(), bucket_ranges_);
Luc Nguyen6458a5c2023-03-31 20:59:17285}
286
bcwhitefa8485b2017-05-01 16:43:25287bool SampleVectorBase::AddSubtractImpl(SampleCountIterator* iter,
288 HistogramSamples::Operator op) {
289 // Stop now if there's nothing to do.
danakjad724a82024-01-25 17:37:40290 if (iter->Done()) {
bcwhitefa8485b2017-05-01 16:43:25291 return true;
danakjad724a82024-01-25 17:37:40292 }
bcwhitefa8485b2017-05-01 16:43:25293
Ramon Cano Apariciob2cba0f2025-01-22 21:26:10294 HistogramBase::Count32 count;
Alexei Svitkine65bd988f2024-09-27 16:08:02295 size_t dest_index = GetDestinationBucketIndexAndCount(*iter, &count);
296 if (dest_index == SIZE_MAX) {
bcwhitefa8485b2017-05-01 16:43:25297 return false;
danakjad724a82024-01-25 17:37:40298 }
bcwhitefa8485b2017-05-01 16:43:25299
300 // Post-increment. Information about the current sample is not available
301 // after this point.
302 iter->Next();
303
304 // Single-value storage is possible if there is no counts storage and the
305 // retrieved entry is the only one in the iterator.
danakjad724a82024-01-25 17:37:40306 if (!counts().has_value()) {
bcwhitefa8485b2017-05-01 16:43:25307 if (iter->Done()) {
308 // Don't call AccumulateSingleSample because that updates sum and count
309 // which was already done by the caller of this method.
310 if (single_sample().Accumulate(
311 dest_index, op == HistogramSamples::ADD ? count : -count)) {
312 // Handle race-condition that mounted counts storage between above and
313 // here.
danakjad724a82024-01-25 17:37:40314 if (counts().has_value()) {
bcwhitefa8485b2017-05-01 16:43:25315 MoveSingleSampleToCounts();
danakjad724a82024-01-25 17:37:40316 }
bcwhitefa8485b2017-05-01 16:43:25317 return true;
318 }
tdanderson94191022017-04-21 23:19:50319 }
bcwhitefa8485b2017-05-01 16:43:25320
321 // The counts storage will be needed to hold the multiple incoming values.
322 MountCountsStorageAndMoveSingleSample();
bcwhite4162baf2017-04-21 18:58:00323 }
tdanderson94191022017-04-21 23:19:50324
bcwhitefa8485b2017-05-01 16:43:25325 // Go through the iterator and add the counts into correct bucket.
326 while (true) {
bcwhitefa8485b2017-05-01 16:43:25327 // Sample's bucket matches exactly. Adjust count.
328 subtle::NoBarrier_AtomicIncrement(
danakjad724a82024-01-25 17:37:40329 &counts_at(dest_index), op == HistogramSamples::ADD ? count : -count);
danakjad724a82024-01-25 17:37:40330 if (iter->Done()) {
bcwhitefa8485b2017-05-01 16:43:25331 return true;
danakjad724a82024-01-25 17:37:40332 }
Alexei Svitkine65bd988f2024-09-27 16:08:02333
334 dest_index = GetDestinationBucketIndexAndCount(*iter, &count);
335 if (dest_index == SIZE_MAX) {
bcwhitefa8485b2017-05-01 16:43:25336 return false;
danakjad724a82024-01-25 17:37:40337 }
bcwhitefa8485b2017-05-01 16:43:25338 iter->Next();
339 }
[email protected]2f7d9cd2012-09-22 03:42:12340}
341
Alexei Svitkine65bd988f2024-09-27 16:08:02342size_t SampleVectorBase::GetDestinationBucketIndexAndCount(
343 SampleCountIterator& iter,
Ramon Cano Apariciob2cba0f2025-01-22 21:26:10344 HistogramBase::Count32* count) {
Ramon Cano Aparicio79e06642025-01-09 19:10:22345 Sample32 min;
Alexei Svitkine65bd988f2024-09-27 16:08:02346 int64_t max;
347
348 iter.Get(&min, &max, count);
349 // If the iter has the bucket index, get there in O(1), otherwise look it up
350 // from the destination via O(logn) binary search.
351 size_t bucket_index;
352 if (!iter.GetBucketIndex(&bucket_index)) {
353 bucket_index = GetBucketIndex(min);
354 }
355
356 // We expect buckets to match between source and destination. If they don't,
357 // we may be trying to merge a different version of a histogram (e.g. two
358 // .pma files from different versions of the code), which is not supported.
359 // We drop the data from the iter in that case.
360 // Technically, this codepath could result in data being partially merged -
361 // i.e. if the buckets at the beginning of iter match, but later ones don't.
362 // As we expect this to be very rare, we intentionally don't handle it to
363 // avoid having to do two iterations through the buckets in AddSubtractImpl().
364 if (bucket_index >= counts_size() ||
365 min != bucket_ranges_->range(bucket_index) ||
366 max != bucket_ranges_->range(bucket_index + 1)) {
367 return SIZE_MAX;
368 }
369 return bucket_index;
370}
371
Weilun Shibf3ceb192020-07-31 22:39:37372// Uses simple binary search or calculates the index directly if it's an "exact"
373// linear histogram. This is very general, but there are better approaches if we
374// knew that the buckets were linearly distributed.
Ramon Cano Aparicio79e06642025-01-09 19:10:22375size_t SampleVectorBase::GetBucketIndex(Sample32 value) const {
[email protected]15ce3842013-06-27 14:38:45376 size_t bucket_count = bucket_ranges_->bucket_count();
[email protected]2f7d9cd2012-09-22 03:42:12377 CHECK_GE(value, bucket_ranges_->range(0));
378 CHECK_LT(value, bucket_ranges_->range(bucket_count));
379
Weilun Shibf3ceb192020-07-31 22:39:37380 // For "exact" linear histograms, e.g. bucket_count = maximum + 1, their
381 // minimum is 1 and bucket sizes are 1. Thus, we don't need to binary search
382 // the bucket index. The bucket index for bucket |value| is just the |value|.
Ramon Cano Aparicio79e06642025-01-09 19:10:22383 Sample32 maximum = bucket_ranges_->range(bucket_count - 1);
384 if (maximum == static_cast<Sample32>(bucket_count - 1)) {
Weilun Shibf3ceb192020-07-31 22:39:37385 // |value| is in the underflow bucket.
danakjad724a82024-01-25 17:37:40386 if (value < 1) {
Weilun Shibf3ceb192020-07-31 22:39:37387 return 0;
danakjad724a82024-01-25 17:37:40388 }
Weilun Shibf3ceb192020-07-31 22:39:37389 // |value| is in the overflow bucket.
danakjad724a82024-01-25 17:37:40390 if (value > maximum) {
Weilun Shibf3ceb192020-07-31 22:39:37391 return bucket_count - 1;
danakjad724a82024-01-25 17:37:40392 }
Weilun Shibf3ceb192020-07-31 22:39:37393 return static_cast<size_t>(value);
394 }
395
[email protected]2f7d9cd2012-09-22 03:42:12396 size_t under = 0;
397 size_t over = bucket_count;
398 size_t mid;
399 do {
400 DCHECK_GE(over, under);
danakjad724a82024-01-25 17:37:40401 mid = under + (over - under) / 2;
402 if (mid == under) {
[email protected]2f7d9cd2012-09-22 03:42:12403 break;
danakjad724a82024-01-25 17:37:40404 }
405 if (bucket_ranges_->range(mid) <= value) {
[email protected]2f7d9cd2012-09-22 03:42:12406 under = mid;
danakjad724a82024-01-25 17:37:40407 } else {
[email protected]2f7d9cd2012-09-22 03:42:12408 over = mid;
danakjad724a82024-01-25 17:37:40409 }
[email protected]2f7d9cd2012-09-22 03:42:12410 } while (true);
411
412 DCHECK_LE(bucket_ranges_->range(mid), value);
413 CHECK_GT(bucket_ranges_->range(mid + 1), value);
414 return mid;
415}
416
bcwhitefa8485b2017-05-01 16:43:25417void SampleVectorBase::MoveSingleSampleToCounts() {
danakjad724a82024-01-25 17:37:40418 DCHECK(counts().has_value());
bcwhitefa8485b2017-05-01 16:43:25419
420 // Disable the single-sample since there is now counts storage for the data.
Luc Nguyen5f4f42c2023-03-24 23:29:07421 SingleSample sample = single_sample().ExtractAndDisable();
bcwhitefa8485b2017-05-01 16:43:25422
423 // Stop here if there is no "count" as trying to find the bucket index of
424 // an invalid (including zero) "value" will crash.
danakjad724a82024-01-25 17:37:40425 if (sample.count == 0) {
bcwhitefa8485b2017-05-01 16:43:25426 return;
danakjad724a82024-01-25 17:37:40427 }
bcwhitefa8485b2017-05-01 16:43:25428
Will Harris552939b2023-02-17 21:09:35429 // Stop here if the sample bucket would be out of range for the AtomicCount
430 // array.
431 if (sample.bucket >= counts_size()) {
432 return;
433 }
434
bcwhitefa8485b2017-05-01 16:43:25435 // Move the value into storage. Sum and redundant-count already account
436 // for this entry so no need to call IncreaseSumAndCount().
danakjad724a82024-01-25 17:37:40437 subtle::NoBarrier_AtomicIncrement(&counts_at(sample.bucket), sample.count);
bcwhitefa8485b2017-05-01 16:43:25438}
439
440void SampleVectorBase::MountCountsStorageAndMoveSingleSample() {
441 // There are many SampleVector objects and the lock is needed very
442 // infrequently (just when advancing from single-sample to multi-sample) so
443 // define a single, global lock that all can use. This lock only prevents
danakjad724a82024-01-25 17:37:40444 // concurrent entry into the code below; access and updates to |counts_data_|
bcwhitefa8485b2017-05-01 16:43:25445 // still requires atomic operations.
446 static LazyInstance<Lock>::Leaky counts_lock = LAZY_INSTANCE_INITIALIZER;
danakjad724a82024-01-25 17:37:40447 if (counts_data_.load(std::memory_order_relaxed) == nullptr) {
bcwhitefa8485b2017-05-01 16:43:25448 AutoLock lock(counts_lock.Get());
danakjad724a82024-01-25 17:37:40449 if (counts_data_.load(std::memory_order_relaxed) == nullptr) {
bcwhitefa8485b2017-05-01 16:43:25450 // Create the actual counts storage while the above lock is acquired.
Ramon Cano Apariciob2cba0f2025-01-22 21:26:10451 span<HistogramBase::Count32> counts = CreateCountsStorageWhileLocked();
danakjad724a82024-01-25 17:37:40452 // Point |counts()| to the newly created storage. This is done while
bcwhitefa8485b2017-05-01 16:43:25453 // locked to prevent possible concurrent calls to CreateCountsStorage
454 // but, between that call and here, other threads could notice the
dschuyler11857732017-06-09 20:45:33455 // existence of the storage and race with this to set_counts(). That's
bcwhitefa8485b2017-05-01 16:43:25456 // okay because (a) it's atomic and (b) it always writes the same value.
457 set_counts(counts);
458 }
459 }
460
461 // Move any single-sample into the newly mounted storage.
462 MoveSingleSampleToCounts();
463}
464
465SampleVector::SampleVector(const BucketRanges* bucket_ranges)
466 : SampleVector(0, bucket_ranges) {}
467
468SampleVector::SampleVector(uint64_t id, const BucketRanges* bucket_ranges)
Arthur Sonzognibc8777c302022-05-24 09:23:06469 : SampleVectorBase(id, std::make_unique<LocalMetadata>(), bucket_ranges) {}
bcwhitefa8485b2017-05-01 16:43:25470
Arthur Sonzognibc8777c302022-05-24 09:23:06471SampleVector::~SampleVector() = default;
bcwhitefa8485b2017-05-01 16:43:25472
Luc Nguyenbbaab292023-09-28 23:30:57473bool SampleVector::IsDefinitelyEmpty() const {
474 // If we are still using SingleSample, and it has a count of 0, then |this|
475 // has no samples. If we are not using SingleSample, always return false, even
476 // though it is possible that |this| has no samples (e.g. we are using a
477 // counts array and all the bucket counts are 0). If we are wrong, this will
478 // just make the caller perform some extra work thinking that |this| is
479 // non-empty.
480 AtomicSingleSample sample = single_sample();
481 return HistogramSamples::IsDefinitelyEmpty() && !sample.IsDisabled() &&
482 sample.Load().count == 0;
483}
484
bcwhitefa8485b2017-05-01 16:43:25485bool SampleVector::MountExistingCountsStorage() const {
486 // There is never any existing storage other than what is already in use.
danakjad724a82024-01-25 17:37:40487 return counts().has_value();
bcwhitefa8485b2017-05-01 16:43:25488}
489
Helmut Januschka274c3802024-03-12 23:31:29490std::string SampleVector::GetAsciiHeader(std::string_view histogram_name,
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38491 int32_t flags) const {
Ramon Cano Apariciob2cba0f2025-01-22 21:26:10492 Count32 sample_count = TotalCount();
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38493 std::string output;
Greg Thompsonde48d062024-01-27 12:19:49494 StrAppend(&output, {"Histogram: ", histogram_name, " recorded ",
495 NumberToString(sample_count), " samples"});
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38496 if (sample_count == 0) {
497 DCHECK_EQ(sum(), 0);
498 } else {
499 double mean = static_cast<float>(sum()) / sample_count;
500 StringAppendF(&output, ", mean = %.1f", mean);
501 }
danakjad724a82024-01-25 17:37:40502 if (flags) {
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38503 StringAppendF(&output, " (flags = 0x%x)", flags);
danakjad724a82024-01-25 17:37:40504 }
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38505 return output;
506}
507
508std::string SampleVector::GetAsciiBody() const {
Ramon Cano Apariciob2cba0f2025-01-22 21:26:10509 Count32 sample_count = TotalCount();
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38510
511 // Prepare to normalize graphical rendering of bucket contents.
512 double max_size = 0;
513 double scaling_factor = 1;
514 max_size = GetPeakBucketSize();
515 // Scale histogram bucket counts to take at most 72 characters.
516 // Note: Keep in sync w/ kLineLength histogram_samples.cc
517 const double kLineLength = 72;
danakjad724a82024-01-25 17:37:40518 if (max_size > kLineLength) {
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38519 scaling_factor = kLineLength / max_size;
danakjad724a82024-01-25 17:37:40520 }
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38521
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38522 // Calculate largest print width needed for any of our bucket range displays.
523 size_t print_width = 1;
524 for (uint32_t i = 0; i < bucket_count(); ++i) {
525 if (GetCountAtIndex(i)) {
526 size_t width =
527 GetSimpleAsciiBucketRange(bucket_ranges()->range(i)).size() + 1;
danakjad724a82024-01-25 17:37:40528 if (width > print_width) {
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38529 print_width = width;
danakjad724a82024-01-25 17:37:40530 }
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38531 }
532 }
533
534 int64_t remaining = sample_count;
535 int64_t past = 0;
536 std::string output;
537 // Output the actual histogram graph.
538 for (uint32_t i = 0; i < bucket_count(); ++i) {
Ramon Cano Apariciob2cba0f2025-01-22 21:26:10539 Count32 current = GetCountAtIndex(i);
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38540 remaining -= current;
541 std::string range = GetSimpleAsciiBucketRange(bucket_ranges()->range(i));
542 output.append(range);
danakjad724a82024-01-25 17:37:40543 for (size_t j = 0; range.size() + j < print_width + 1; ++j) {
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38544 output.push_back(' ');
danakjad724a82024-01-25 17:37:40545 }
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38546 if (0 == current && i < bucket_count() - 1 && 0 == GetCountAtIndex(i + 1)) {
547 while (i < bucket_count() - 1 && 0 == GetCountAtIndex(i + 1)) {
548 ++i;
549 }
550 output.append("... \n");
551 continue; // No reason to plot emptiness.
552 }
Ramon Cano Apariciob2cba0f2025-01-22 21:26:10553 Count32 current_size = round(current * scaling_factor);
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38554 WriteAsciiBucketGraph(current_size, kLineLength, &output);
555 WriteAsciiBucketContext(past, current, remaining, i, &output);
556 output.append("\n");
557 past += current;
558 }
559 DCHECK_EQ(sample_count, past);
560 return output;
561}
562
563double SampleVector::GetPeakBucketSize() const {
Ramon Cano Apariciob2cba0f2025-01-22 21:26:10564 Count32 max = 0;
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38565 for (uint32_t i = 0; i < bucket_count(); ++i) {
Ramon Cano Apariciob2cba0f2025-01-22 21:26:10566 Count32 current = GetCountAtIndex(i);
danakjad724a82024-01-25 17:37:40567 if (current > max) {
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38568 max = current;
danakjad724a82024-01-25 17:37:40569 }
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38570 }
571 return max;
572}
573
574void SampleVector::WriteAsciiBucketContext(int64_t past,
Ramon Cano Apariciob2cba0f2025-01-22 21:26:10575 Count32 current,
Quang Minh Tuan Nguyen39b1fed2021-03-16 00:49:38576 int64_t remaining,
577 uint32_t current_bucket_index,
578 std::string* output) const {
579 double scaled_sum = (past + current + remaining) / 100.0;
580 WriteAsciiBucketValue(current, scaled_sum, output);
581 if (0 < current_bucket_index) {
582 double percentage = past / scaled_sum;
583 StringAppendF(output, " {%3.1f%%}", percentage);
584 }
585}
586
danakjad724a82024-01-25 17:37:40587span<HistogramBase::AtomicCount>
588SampleVector::CreateCountsStorageWhileLocked() {
bcwhitefa8485b2017-05-01 16:43:25589 local_counts_.resize(counts_size());
danakjad724a82024-01-25 17:37:40590 return local_counts_;
bcwhitefa8485b2017-05-01 16:43:25591}
592
593PersistentSampleVector::PersistentSampleVector(
Roger McFarlaned9048a02025-04-14 16:22:01594 std::string_view name,
bcwhitefa8485b2017-05-01 16:43:25595 uint64_t id,
596 const BucketRanges* bucket_ranges,
597 Metadata* meta,
598 const DelayedPersistentAllocation& counts)
599 : SampleVectorBase(id, meta, bucket_ranges), persistent_counts_(counts) {
600 // Only mount the full storage if the single-sample has been disabled.
601 // Otherwise, it is possible for this object instance to start using (empty)
602 // storage that was created incidentally while another instance continues to
603 // update to the single sample. This "incidental creation" can happen because
604 // the memory is a DelayedPersistentAllocation which allows multiple memory
605 // blocks within it and applies an all-or-nothing approach to the allocation.
606 // Thus, a request elsewhere for one of the _other_ blocks would make _this_
607 // block available even though nothing has explicitly requested it.
608 //
609 // Note that it's not possible for the ctor to mount existing storage and
610 // move any single-sample to it because sometimes the persistent memory is
611 // read-only. Only non-const methods (which assume that memory is read/write)
612 // can do that.
Roger McFarlane58b747d692025-05-16 00:07:01613 if (single_sample().IsDisabled()) {
614 const auto result = MountExistingCountsStorageImpl();
615
616 // Record the result of MountExistingCountsStorageImpl() to the
617 // kMountExistingCountsStorageResult histogram so that we can see how often
618 // the call to MountExistingCountsStorageImpl() fails.
619 //
620 // See crbug.com/410544723
621 //
622 // Recording a histogram here is safe for re-entrancy because the act of
623 // recording the histogram will either:
624 // * Create a new histogram with no pre-existing samples, so the above check
625 // for `single_sample().IsDisabled()` will be `false` when reached; or,
626 // * Find the existing histogram, without calling this constructor.
627 RecordMountExistingCountsStorageResult(result);
628
629 // We're trying to understand how/why the call to MountExistingCountsStorage
630 // sometimes fails. We've seen enough examples of kNothingToRead to suggest
631 // that this specific failure is not strongly associated with any particular
632 // historam being recovered; so, we don't need to collect any further crash
633 // dumps for that outcome. We haven't seen many examples of kCorrupt, so we
634 // continue to collect those.
Roger McFarlaned9048a02025-04-14 16:22:01635 // TODO: crbug.com/410544723 - Remove crash keys and DumpWithoutCrashing
636 // once investigation is complete.
Roger McFarlane58b747d692025-05-16 00:07:01637 if (result != MountExistingCountsStorageResult::kSucceeded &&
638 result != MountExistingCountsStorageResult::kNothingToRead) {
639#if !BUILDFLAG(IS_NACL)
640 SCOPED_CRASH_KEY_STRING64("PSV", "name", name);
641 SCOPED_CRASH_KEY_NUMBER("PSV", "counts_ref",
642 persistent_counts_.reference());
Roger McFarlaned9048a02025-04-14 16:22:01643#endif
Roger McFarlane58b747d692025-05-16 00:07:01644 debug::DumpWithoutCrashing();
645 }
bcwhitefa8485b2017-05-01 16:43:25646 }
647}
648
Chris Watkinsbb7211c2017-11-29 07:16:38649PersistentSampleVector::~PersistentSampleVector() = default;
bcwhitefa8485b2017-05-01 16:43:25650
Luc Nguyenbbaab292023-09-28 23:30:57651bool PersistentSampleVector::IsDefinitelyEmpty() const {
652 // Not implemented.
Peter Boströmde573332024-08-26 20:42:45653 NOTREACHED();
Luc Nguyenbbaab292023-09-28 23:30:57654}
655
Roger McFarlane58b747d692025-05-16 00:07:01656// static
657constinit std::atomic_uintptr_t
658 PersistentSampleVector::atomic_histogram_pointer{0};
659
660// static
661void PersistentSampleVector::ResetMountExistingCountsStorageResultForTesting() {
662 atomic_histogram_pointer.store(0, std::memory_order_release);
663}
664
665// static
666void PersistentSampleVector::RecordMountExistingCountsStorageResult(
667 MountExistingCountsStorageResult result) {
668 static constexpr auto boundary = static_cast<base::HistogramBase::Sample32>(
669 MountExistingCountsStorageResult::kMaxValue);
670 // This method is the functional and performance equivalent of:
671 //
672 // UMA_HISTOGRAM_ENUMERATION(kMountExistingCountsStorageResult, result);
673 //
674 // The UMA_HISTOGRAM_ENUMERATION macro hides the static pointer used to cache
675 // the histogram pointer. We need to be able to reset the cached histogram
676 // pointer for testing so we have extracted the histogram pointer to a static
677 // member and used HISTOGRAM_POINTER_USE macro to complete the implementation,
678 // which is ultimately what UMA_HISTOGRAM_ENUMERATION macro does.
679 HISTOGRAM_POINTER_USE(
680 std::addressof(atomic_histogram_pointer),
681 kMountExistingCountsStorageResult,
682 Add(static_cast<base::HistogramBase::Sample32>(result)),
683 base::LinearHistogram::FactoryGet(
684 kMountExistingCountsStorageResult, 1, boundary, boundary + 1,
685 base::HistogramBase::kUmaTargetedHistogramFlag));
686}
687
688PersistentSampleVector::MountExistingCountsStorageResult
689PersistentSampleVector::MountExistingCountsStorageImpl() const {
bcwhitefa8485b2017-05-01 16:43:25690 // There is no early exit if counts is not yet mounted because, given that
691 // this is a virtual function, it's more efficient to do that at the call-
692 // site. There is no danger, however, should this get called anyway (perhaps
danakjad724a82024-01-25 17:37:40693 // because of a race condition) because at worst the `counts_data_` and
694 // `counts_size_` members would be over-written (in an atomic manner)
695 // with the exact same values.
bcwhitefa8485b2017-05-01 16:43:25696
danakjad724a82024-01-25 17:37:40697 if (!persistent_counts_.reference()) {
Roger McFarlane58b747d692025-05-16 00:07:01698 return MountExistingCountsStorageResult::kNothingToRead;
bcwhitefa8485b2017-05-01 16:43:25699 }
700
danakjad724a82024-01-25 17:37:40701 // Mount the counts array in position. This shouldn't fail but can if the
702 // data is corrupt or incomplete.
703 span<HistogramBase::AtomicCount> mem =
704 persistent_counts_.Get<HistogramBase::AtomicCount>();
705 if (mem.empty()) {
Roger McFarlane58b747d692025-05-16 00:07:01706 return MountExistingCountsStorageResult::kCorrupt;
danakjad724a82024-01-25 17:37:40707 }
708 // Uses a span that only covers the counts the SampleVector should have
709 // access to, which can be a subset of the entire persistent allocation.
710 set_counts(mem.first(counts_size()));
Roger McFarlane58b747d692025-05-16 00:07:01711 return MountExistingCountsStorageResult::kSucceeded;
712}
713
714bool PersistentSampleVector::MountExistingCountsStorage() const {
715 return MountExistingCountsStorageImpl() ==
716 MountExistingCountsStorageResult::kSucceeded;
danakjad724a82024-01-25 17:37:40717}
718
719span<HistogramBase::AtomicCount>
720PersistentSampleVector::CreateCountsStorageWhileLocked() {
721 span<HistogramBase::AtomicCount> mem =
722 persistent_counts_.Get<HistogramBase::AtomicCount>();
723 if (mem.empty()) {
724 // The above shouldn't fail but can if Bad Things(tm) are occurring in
725 // the persistent allocator. Crashing isn't a good option so instead
726 // just allocate something from the heap that we will leak and return that.
727 // There will be no sharing or persistence but worse things are already
728 // happening.
729 auto array = HeapArray<HistogramBase::AtomicCount>::WithSize(counts_size());
Alexei Svitkine2d87fb5a2024-03-04 16:49:38730 ANNOTATE_LEAKING_OBJECT_PTR(array.data());
danakjad724a82024-01-25 17:37:40731 return std::move(array).leak();
732 }
733
734 // Returns a span that only covers the counts the SampleVector should have
735 // access to, which can be a subset of the entire persistent allocation.
736 return mem.first(counts_size());
bcwhitefa8485b2017-05-01 16:43:25737}
738
[email protected]2f7d9cd2012-09-22 03:42:12739} // namespace base