Avi Drissman | e4622aa | 2022-09-08 20:36:06 | [diff] [blame] | 1 | // Copyright 2012 The Chromium Authors |
[email protected] | 2f7d9cd | 2012-09-22 03:42:12 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "base/metrics/sample_vector.h" |
| 6 | |
Jean-Philippe Gravel | d90e05ca | 2022-11-27 20:29:47 | [diff] [blame] | 7 | #include <ostream> |
Helmut Januschka | 274c380 | 2024-03-12 23:31:29 | [diff] [blame] | 8 | #include <string_view> |
Peter Kasting | a461403 | 2025-01-07 17:51:27 | [diff] [blame] | 9 | #include <type_traits> |
Jean-Philippe Gravel | d90e05ca | 2022-11-27 20:29:47 | [diff] [blame] | 10 | |
Hans Wennborg | c3cffa6 | 2020-04-27 10:09:12 | [diff] [blame] | 11 | #include "base/check_op.h" |
Peter McNeeley | 2e1581a | 2023-12-14 22:57:15 | [diff] [blame] | 12 | #include "base/compiler_specific.h" |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 13 | #include "base/containers/heap_array.h" |
Luc Nguyen | 8084cab5 | 2023-04-14 16:20:56 | [diff] [blame] | 14 | #include "base/debug/crash_logging.h" |
Roger McFarlane | 81d6769 | 2025-04-09 22:33:41 | [diff] [blame] | 15 | #include "base/debug/dump_without_crashing.h" |
Alexei Svitkine | 2d87fb5a | 2024-03-04 16:49:38 | [diff] [blame] | 16 | #include "base/debug/leak_annotations.h" |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 17 | #include "base/lazy_instance.h" |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 18 | #include "base/memory/ptr_util.h" |
Tom Sepez | 7a008da | 2023-11-07 21:02:33 | [diff] [blame] | 19 | #include "base/memory/raw_span.h" |
Alexei Svitkine | e806b98 | 2024-09-20 14:49:22 | [diff] [blame] | 20 | #include "base/metrics/histogram_macros.h" |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 21 | #include "base/metrics/persistent_memory_allocator.h" |
Hans Wennborg | c3cffa6 | 2020-04-27 10:09:12 | [diff] [blame] | 22 | #include "base/notreached.h" |
asvitkine | 3f17b146 | 2017-05-03 21:37:37 | [diff] [blame] | 23 | #include "base/numerics/safe_conversions.h" |
Greg Thompson | de48d06 | 2024-01-27 12:19:49 | [diff] [blame] | 24 | #include "base/strings/strcat.h" |
| 25 | #include "base/strings/string_number_conversions.h" |
Quang Minh Tuan Nguyen | 39b1fed | 2021-03-16 00:49:38 | [diff] [blame] | 26 | #include "base/strings/stringprintf.h" |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 27 | #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] | 2f7d9cd | 2012-09-22 03:42:12 | [diff] [blame] | 35 | |
[email protected] | 2f7d9cd | 2012-09-22 03:42:12 | [diff] [blame] | 36 | namespace base { |
| 37 | |
Ramon Cano Aparicio | b2cba0f | 2025-01-22 21:26:10 | [diff] [blame] | 38 | typedef HistogramBase::Count32 Count32; |
Ramon Cano Aparicio | 79e0664 | 2025-01-09 19:10:22 | [diff] [blame] | 39 | typedef HistogramBase::Sample32 Sample32; |
[email protected] | 2f7d9cd | 2012-09-22 03:42:12 | [diff] [blame] | 40 | |
Luc Nguyen | 60b27cb | 2023-03-29 15:22:56 | [diff] [blame] | 41 | namespace { |
| 42 | |
| 43 | // An iterator for sample vectors. |
Peter Kasting | a461403 | 2025-01-07 17:51:27 | [diff] [blame] | 44 | template <bool support_extraction> |
| 45 | class SampleVectorIterator : public SampleCountIterator { |
| 46 | private: |
| 47 | using T = std::conditional_t<support_extraction, |
| 48 | HistogramBase::AtomicCount, |
| 49 | const HistogramBase::AtomicCount>; |
| 50 | |
Luc Nguyen | 60b27cb | 2023-03-29 15:22:56 | [diff] [blame] | 51 | public: |
Peter Kasting | a461403 | 2025-01-07 17:51:27 | [diff] [blame] | 52 | SampleVectorIterator(base::span<T> counts, const BucketRanges* bucket_ranges) |
Tom Sepez | 7a008da | 2023-11-07 21:02:33 | [diff] [blame] | 53 | : counts_(counts), bucket_ranges_(bucket_ranges) { |
Luc Nguyen | 60b27cb | 2023-03-29 15:22:56 | [diff] [blame] | 54 | SkipEmptyBuckets(); |
| 55 | } |
| 56 | |
Peter Kasting | a461403 | 2025-01-07 17:51:27 | [diff] [blame] | 57 | ~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 Nguyen | 60b27cb | 2023-03-29 15:22:56 | [diff] [blame] | 64 | |
| 65 | // SampleCountIterator: |
Tom Sepez | 7a008da | 2023-11-07 21:02:33 | [diff] [blame] | 66 | bool Done() const override { return index_ >= counts_.size(); } |
Luc Nguyen | 60b27cb | 2023-03-29 15:22:56 | [diff] [blame] | 67 | void Next() override { |
| 68 | DCHECK(!Done()); |
Peter Kasting | a461403 | 2025-01-07 17:51:27 | [diff] [blame] | 69 | ++index_; |
Luc Nguyen | 60b27cb | 2023-03-29 15:22:56 | [diff] [blame] | 70 | SkipEmptyBuckets(); |
| 71 | } |
Ramon Cano Aparicio | 79e0664 | 2025-01-09 19:10:22 | [diff] [blame] | 72 | void Get(Sample32* min, |
Luc Nguyen | 60b27cb | 2023-03-29 15:22:56 | [diff] [blame] | 73 | int64_t* max, |
Ramon Cano Aparicio | b2cba0f | 2025-01-22 21:26:10 | [diff] [blame] | 74 | HistogramBase::Count32* count) override { |
Peter Kasting | a461403 | 2025-01-07 17:51:27 | [diff] [blame] | 75 | 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 Nguyen | 60b27cb | 2023-03-29 15:22:56 | [diff] [blame] | 84 | |
| 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 Sepez | 7a008da | 2023-11-07 21:02:33 | [diff] [blame] | 100 | while (index_ < counts_.size()) { |
Luc Nguyen | 60b27cb | 2023-03-29 15:22:56 | [diff] [blame] | 101 | if (subtle::NoBarrier_Load(&counts_[index_]) != 0) { |
| 102 | return; |
| 103 | } |
Peter Kasting | a461403 | 2025-01-07 17:51:27 | [diff] [blame] | 104 | ++index_; |
Luc Nguyen | 60b27cb | 2023-03-29 15:22:56 | [diff] [blame] | 105 | } |
| 106 | } |
| 107 | |
Tom Sepez | 7a008da | 2023-11-07 21:02:33 | [diff] [blame] | 108 | raw_span<T> counts_; |
Luc Nguyen | 60b27cb | 2023-03-29 15:22:56 | [diff] [blame] | 109 | raw_ptr<const BucketRanges> bucket_ranges_; |
Luc Nguyen | 60b27cb | 2023-03-29 15:22:56 | [diff] [blame] | 110 | size_t index_ = 0; |
| 111 | }; |
| 112 | |
| 113 | } // namespace |
| 114 | |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 115 | SampleVectorBase::SampleVectorBase(uint64_t id, |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 116 | Metadata* meta, |
| 117 | const BucketRanges* bucket_ranges) |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 118 | : HistogramSamples(id, meta), |
| 119 | bucket_ranges_(bucket_ranges), |
| 120 | counts_size_(bucket_ranges_->bucket_count()) { |
| 121 | CHECK_GE(counts_size_, 1u); |
bcwhite | b036e432 | 2015-12-10 18:36:34 | [diff] [blame] | 122 | } |
| 123 | |
Arthur Sonzogni | bc8777c30 | 2022-05-24 09:23:06 | [diff] [blame] | 124 | SampleVectorBase::SampleVectorBase(uint64_t id, |
| 125 | std::unique_ptr<Metadata> meta, |
| 126 | const BucketRanges* bucket_ranges) |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 127 | : HistogramSamples(id, std::move(meta)), |
| 128 | bucket_ranges_(bucket_ranges), |
| 129 | counts_size_(bucket_ranges_->bucket_count()) { |
| 130 | CHECK_GE(counts_size_, 1u); |
Arthur Sonzogni | bc8777c30 | 2022-05-24 09:23:06 | [diff] [blame] | 131 | } |
| 132 | |
Chris Watkins | bb7211c | 2017-11-29 07:16:38 | [diff] [blame] | 133 | SampleVectorBase::~SampleVectorBase() = default; |
[email protected] | 2f7d9cd | 2012-09-22 03:42:12 | [diff] [blame] | 134 | |
Ramon Cano Aparicio | b2cba0f | 2025-01-22 21:26:10 | [diff] [blame] | 135 | void SampleVectorBase::Accumulate(Sample32 value, Count32 count) { |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 136 | const size_t bucket_index = GetBucketIndex(value); |
[email protected] | 2f7d9cd | 2012-09-22 03:42:12 | [diff] [blame] | 137 | |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 138 | // Handle the single-sample case. |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 139 | if (!counts().has_value()) { |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 140 | // 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. |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 147 | if (counts().has_value()) { |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 148 | MoveSingleSampleToCounts(); |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 149 | } |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 150 | return; |
| 151 | } |
[email protected] | 2f7d9cd | 2012-09-22 03:42:12 | [diff] [blame] | 152 | |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 153 | // Need real storage to store both what was in the single-sample plus the |
| 154 | // parameter information. |
| 155 | MountCountsStorageAndMoveSingleSample(); |
[email protected] | 2f7d9cd | 2012-09-22 03:42:12 | [diff] [blame] | 156 | } |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 157 | |
| 158 | // Handle the multi-sample case. |
Ramon Cano Aparicio | b2cba0f | 2025-01-22 21:26:10 | [diff] [blame] | 159 | Count32 new_bucket_count = |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 160 | subtle::NoBarrier_AtomicIncrement(&counts_at(bucket_index), count); |
asvitkine | 3f17b146 | 2017-05-03 21:37:37 | [diff] [blame] | 161 | IncreaseSumAndCount(strict_cast<int64_t>(count) * value, count); |
Brian White | 537e41a | 2017-11-01 03:02:13 | [diff] [blame] | 162 | |
| 163 | // TODO(bcwhite) Remove after crbug.com/682680. |
Ramon Cano Aparicio | b2cba0f | 2025-01-22 21:26:10 | [diff] [blame] | 164 | Count32 old_bucket_count = new_bucket_count - count; |
Peter McNeeley | 2e1581a | 2023-12-14 22:57:15 | [diff] [blame] | 165 | bool record_negative_sample = |
| 166 | (new_bucket_count >= 0) != (old_bucket_count >= 0) && count > 0; |
Peter Kasting | fa48899 | 2024-08-06 07:48:14 | [diff] [blame] | 167 | if (record_negative_sample) [[unlikely]] { |
Brian White | 537e41a | 2017-11-01 03:02:13 | [diff] [blame] | 168 | RecordNegativeSample(SAMPLES_ACCUMULATE_OVERFLOW, count); |
Peter McNeeley | 2e1581a | 2023-12-14 22:57:15 | [diff] [blame] | 169 | } |
[email protected] | 2f7d9cd | 2012-09-22 03:42:12 | [diff] [blame] | 170 | } |
| 171 | |
Ramon Cano Aparicio | b2cba0f | 2025-01-22 21:26:10 | [diff] [blame] | 172 | Count32 SampleVectorBase::GetCount(Sample32 value) const { |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 173 | return GetCountAtIndex(GetBucketIndex(value)); |
[email protected] | 2f7d9cd | 2012-09-22 03:42:12 | [diff] [blame] | 174 | } |
| 175 | |
Ramon Cano Aparicio | b2cba0f | 2025-01-22 21:26:10 | [diff] [blame] | 176 | Count32 SampleVectorBase::TotalCount() const { |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 177 | // Handle the single-sample case. |
| 178 | SingleSample sample = single_sample().Load(); |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 179 | if (sample.count != 0) { |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 180 | return sample.count; |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 181 | } |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 182 | |
| 183 | // Handle the multi-sample case. |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 184 | if (counts().has_value() || MountExistingCountsStorage()) { |
Ramon Cano Aparicio | b2cba0f | 2025-01-22 21:26:10 | [diff] [blame] | 185 | Count32 count = 0; |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 186 | // 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); |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 191 | } |
| 192 | return count; |
| 193 | } |
| 194 | |
| 195 | // And the no-value case. |
| 196 | return 0; |
[email protected] | 2f7d9cd | 2012-09-22 03:42:12 | [diff] [blame] | 197 | } |
| 198 | |
Ramon Cano Aparicio | b2cba0f | 2025-01-22 21:26:10 | [diff] [blame] | 199 | Count32 SampleVectorBase::GetCountAtIndex(size_t bucket_index) const { |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 200 | DCHECK(bucket_index < counts_size()); |
| 201 | |
| 202 | // Handle the single-sample case. |
| 203 | SingleSample sample = single_sample().Load(); |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 204 | if (sample.count != 0) { |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 205 | return sample.bucket == bucket_index ? sample.count : 0; |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 206 | } |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 207 | |
| 208 | // Handle the multi-sample case. |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 209 | if (counts().has_value() || MountExistingCountsStorage()) { |
| 210 | return subtle::NoBarrier_Load(&counts_at(bucket_index)); |
| 211 | } |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 212 | |
| 213 | // And the no-value case. |
| 214 | return 0; |
| 215 | } |
| 216 | |
| 217 | std::unique_ptr<SampleCountIterator> SampleVectorBase::Iterator() const { |
| 218 | // Handle the single-sample case. |
| 219 | SingleSample sample = single_sample().Load(); |
| 220 | if (sample.count != 0) { |
Luc Nguyen | 415ee2cf | 2024-03-07 17:47:10 | [diff] [blame] | 221 | 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 Kasting | a461403 | 2025-01-07 17:51:27 | [diff] [blame] | 227 | return std::make_unique<SampleVectorIterator<false>>( |
Luc Nguyen | 415ee2cf | 2024-03-07 17:47:10 | [diff] [blame] | 228 | base::span<const HistogramBase::AtomicCount>(), bucket_ranges_); |
| 229 | } |
| 230 | |
Jeremy Roman | 9532f25 | 2017-08-16 23:27:24 | [diff] [blame] | 231 | return std::make_unique<SingleSampleIterator>( |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 232 | bucket_ranges_->range(sample.bucket), |
Luc Nguyen | 6458a5c | 2023-03-31 20:59:17 | [diff] [blame] | 233 | bucket_ranges_->range(sample.bucket + 1), sample.count, sample.bucket, |
| 234 | /*value_was_extracted=*/false); |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 235 | } |
| 236 | |
| 237 | // Handle the multi-sample case. |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 238 | if (counts().has_value() || MountExistingCountsStorage()) { |
Peter Kasting | a461403 | 2025-01-07 17:51:27 | [diff] [blame] | 239 | return std::make_unique<SampleVectorIterator<false>>(*counts(), |
| 240 | bucket_ranges_); |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 241 | } |
| 242 | |
| 243 | // And the no-value case. |
Peter Kasting | a461403 | 2025-01-07 17:51:27 | [diff] [blame] | 244 | return std::make_unique<SampleVectorIterator<false>>( |
Tom Sepez | 7a008da | 2023-11-07 21:02:33 | [diff] [blame] | 245 | base::span<const HistogramBase::AtomicCount>(), bucket_ranges_); |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 246 | } |
| 247 | |
Luc Nguyen | 6458a5c | 2023-03-31 20:59:17 | [diff] [blame] | 248 | std::unique_ptr<SampleCountIterator> SampleVectorBase::ExtractingIterator() { |
| 249 | // Handle the single-sample case. |
| 250 | SingleSample sample = single_sample().Extract(); |
| 251 | if (sample.count != 0) { |
Luc Nguyen | 415ee2cf | 2024-03-07 17:47:10 | [diff] [blame] | 252 | 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 Kasting | a461403 | 2025-01-07 17:51:27 | [diff] [blame] | 257 | return std::make_unique<SampleVectorIterator<true>>( |
Luc Nguyen | 415ee2cf | 2024-03-07 17:47:10 | [diff] [blame] | 258 | base::span<HistogramBase::AtomicCount>(), bucket_ranges_); |
| 259 | } |
| 260 | |
Luc Nguyen | 6458a5c | 2023-03-31 20:59:17 | [diff] [blame] | 261 | // 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. |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 277 | if (counts().has_value() || MountExistingCountsStorage()) { |
Peter Kasting | a461403 | 2025-01-07 17:51:27 | [diff] [blame] | 278 | return std::make_unique<SampleVectorIterator<true>>(*counts(), |
| 279 | bucket_ranges_); |
Luc Nguyen | 6458a5c | 2023-03-31 20:59:17 | [diff] [blame] | 280 | } |
| 281 | |
| 282 | // And the no-value case. |
Peter Kasting | a461403 | 2025-01-07 17:51:27 | [diff] [blame] | 283 | return std::make_unique<SampleVectorIterator<true>>( |
Tom Sepez | 7a008da | 2023-11-07 21:02:33 | [diff] [blame] | 284 | base::span<HistogramBase::AtomicCount>(), bucket_ranges_); |
Luc Nguyen | 6458a5c | 2023-03-31 20:59:17 | [diff] [blame] | 285 | } |
| 286 | |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 287 | bool SampleVectorBase::AddSubtractImpl(SampleCountIterator* iter, |
| 288 | HistogramSamples::Operator op) { |
| 289 | // Stop now if there's nothing to do. |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 290 | if (iter->Done()) { |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 291 | return true; |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 292 | } |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 293 | |
Ramon Cano Aparicio | b2cba0f | 2025-01-22 21:26:10 | [diff] [blame] | 294 | HistogramBase::Count32 count; |
Alexei Svitkine | 65bd988f | 2024-09-27 16:08:02 | [diff] [blame] | 295 | size_t dest_index = GetDestinationBucketIndexAndCount(*iter, &count); |
| 296 | if (dest_index == SIZE_MAX) { |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 297 | return false; |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 298 | } |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 299 | |
| 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. |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 306 | if (!counts().has_value()) { |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 307 | 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. |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 314 | if (counts().has_value()) { |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 315 | MoveSingleSampleToCounts(); |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 316 | } |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 317 | return true; |
| 318 | } |
tdanderson | 9419102 | 2017-04-21 23:19:50 | [diff] [blame] | 319 | } |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 320 | |
| 321 | // The counts storage will be needed to hold the multiple incoming values. |
| 322 | MountCountsStorageAndMoveSingleSample(); |
bcwhite | 4162baf | 2017-04-21 18:58:00 | [diff] [blame] | 323 | } |
tdanderson | 9419102 | 2017-04-21 23:19:50 | [diff] [blame] | 324 | |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 325 | // Go through the iterator and add the counts into correct bucket. |
| 326 | while (true) { |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 327 | // Sample's bucket matches exactly. Adjust count. |
| 328 | subtle::NoBarrier_AtomicIncrement( |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 329 | &counts_at(dest_index), op == HistogramSamples::ADD ? count : -count); |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 330 | if (iter->Done()) { |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 331 | return true; |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 332 | } |
Alexei Svitkine | 65bd988f | 2024-09-27 16:08:02 | [diff] [blame] | 333 | |
| 334 | dest_index = GetDestinationBucketIndexAndCount(*iter, &count); |
| 335 | if (dest_index == SIZE_MAX) { |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 336 | return false; |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 337 | } |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 338 | iter->Next(); |
| 339 | } |
[email protected] | 2f7d9cd | 2012-09-22 03:42:12 | [diff] [blame] | 340 | } |
| 341 | |
Alexei Svitkine | 65bd988f | 2024-09-27 16:08:02 | [diff] [blame] | 342 | size_t SampleVectorBase::GetDestinationBucketIndexAndCount( |
| 343 | SampleCountIterator& iter, |
Ramon Cano Aparicio | b2cba0f | 2025-01-22 21:26:10 | [diff] [blame] | 344 | HistogramBase::Count32* count) { |
Ramon Cano Aparicio | 79e0664 | 2025-01-09 19:10:22 | [diff] [blame] | 345 | Sample32 min; |
Alexei Svitkine | 65bd988f | 2024-09-27 16:08:02 | [diff] [blame] | 346 | 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 Shi | bf3ceb19 | 2020-07-31 22:39:37 | [diff] [blame] | 372 | // 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 Aparicio | 79e0664 | 2025-01-09 19:10:22 | [diff] [blame] | 375 | size_t SampleVectorBase::GetBucketIndex(Sample32 value) const { |
[email protected] | 15ce384 | 2013-06-27 14:38:45 | [diff] [blame] | 376 | size_t bucket_count = bucket_ranges_->bucket_count(); |
[email protected] | 2f7d9cd | 2012-09-22 03:42:12 | [diff] [blame] | 377 | CHECK_GE(value, bucket_ranges_->range(0)); |
| 378 | CHECK_LT(value, bucket_ranges_->range(bucket_count)); |
| 379 | |
Weilun Shi | bf3ceb19 | 2020-07-31 22:39:37 | [diff] [blame] | 380 | // 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 Aparicio | 79e0664 | 2025-01-09 19:10:22 | [diff] [blame] | 383 | Sample32 maximum = bucket_ranges_->range(bucket_count - 1); |
| 384 | if (maximum == static_cast<Sample32>(bucket_count - 1)) { |
Weilun Shi | bf3ceb19 | 2020-07-31 22:39:37 | [diff] [blame] | 385 | // |value| is in the underflow bucket. |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 386 | if (value < 1) { |
Weilun Shi | bf3ceb19 | 2020-07-31 22:39:37 | [diff] [blame] | 387 | return 0; |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 388 | } |
Weilun Shi | bf3ceb19 | 2020-07-31 22:39:37 | [diff] [blame] | 389 | // |value| is in the overflow bucket. |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 390 | if (value > maximum) { |
Weilun Shi | bf3ceb19 | 2020-07-31 22:39:37 | [diff] [blame] | 391 | return bucket_count - 1; |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 392 | } |
Weilun Shi | bf3ceb19 | 2020-07-31 22:39:37 | [diff] [blame] | 393 | return static_cast<size_t>(value); |
| 394 | } |
| 395 | |
[email protected] | 2f7d9cd | 2012-09-22 03:42:12 | [diff] [blame] | 396 | size_t under = 0; |
| 397 | size_t over = bucket_count; |
| 398 | size_t mid; |
| 399 | do { |
| 400 | DCHECK_GE(over, under); |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 401 | mid = under + (over - under) / 2; |
| 402 | if (mid == under) { |
[email protected] | 2f7d9cd | 2012-09-22 03:42:12 | [diff] [blame] | 403 | break; |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 404 | } |
| 405 | if (bucket_ranges_->range(mid) <= value) { |
[email protected] | 2f7d9cd | 2012-09-22 03:42:12 | [diff] [blame] | 406 | under = mid; |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 407 | } else { |
[email protected] | 2f7d9cd | 2012-09-22 03:42:12 | [diff] [blame] | 408 | over = mid; |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 409 | } |
[email protected] | 2f7d9cd | 2012-09-22 03:42:12 | [diff] [blame] | 410 | } 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 | |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 417 | void SampleVectorBase::MoveSingleSampleToCounts() { |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 418 | DCHECK(counts().has_value()); |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 419 | |
| 420 | // Disable the single-sample since there is now counts storage for the data. |
Luc Nguyen | 5f4f42c | 2023-03-24 23:29:07 | [diff] [blame] | 421 | SingleSample sample = single_sample().ExtractAndDisable(); |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 422 | |
| 423 | // Stop here if there is no "count" as trying to find the bucket index of |
| 424 | // an invalid (including zero) "value" will crash. |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 425 | if (sample.count == 0) { |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 426 | return; |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 427 | } |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 428 | |
Will Harris | 552939b | 2023-02-17 21:09:35 | [diff] [blame] | 429 | // 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 | |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 435 | // Move the value into storage. Sum and redundant-count already account |
| 436 | // for this entry so no need to call IncreaseSumAndCount(). |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 437 | subtle::NoBarrier_AtomicIncrement(&counts_at(sample.bucket), sample.count); |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 438 | } |
| 439 | |
| 440 | void 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 |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 444 | // concurrent entry into the code below; access and updates to |counts_data_| |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 445 | // still requires atomic operations. |
| 446 | static LazyInstance<Lock>::Leaky counts_lock = LAZY_INSTANCE_INITIALIZER; |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 447 | if (counts_data_.load(std::memory_order_relaxed) == nullptr) { |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 448 | AutoLock lock(counts_lock.Get()); |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 449 | if (counts_data_.load(std::memory_order_relaxed) == nullptr) { |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 450 | // Create the actual counts storage while the above lock is acquired. |
Ramon Cano Aparicio | b2cba0f | 2025-01-22 21:26:10 | [diff] [blame] | 451 | span<HistogramBase::Count32> counts = CreateCountsStorageWhileLocked(); |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 452 | // Point |counts()| to the newly created storage. This is done while |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 453 | // locked to prevent possible concurrent calls to CreateCountsStorage |
| 454 | // but, between that call and here, other threads could notice the |
dschuyler | 1185773 | 2017-06-09 20:45:33 | [diff] [blame] | 455 | // existence of the storage and race with this to set_counts(). That's |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 456 | // 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 | |
| 465 | SampleVector::SampleVector(const BucketRanges* bucket_ranges) |
| 466 | : SampleVector(0, bucket_ranges) {} |
| 467 | |
| 468 | SampleVector::SampleVector(uint64_t id, const BucketRanges* bucket_ranges) |
Arthur Sonzogni | bc8777c30 | 2022-05-24 09:23:06 | [diff] [blame] | 469 | : SampleVectorBase(id, std::make_unique<LocalMetadata>(), bucket_ranges) {} |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 470 | |
Arthur Sonzogni | bc8777c30 | 2022-05-24 09:23:06 | [diff] [blame] | 471 | SampleVector::~SampleVector() = default; |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 472 | |
Luc Nguyen | bbaab29 | 2023-09-28 23:30:57 | [diff] [blame] | 473 | bool 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 | |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 485 | bool SampleVector::MountExistingCountsStorage() const { |
| 486 | // There is never any existing storage other than what is already in use. |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 487 | return counts().has_value(); |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 488 | } |
| 489 | |
Helmut Januschka | 274c380 | 2024-03-12 23:31:29 | [diff] [blame] | 490 | std::string SampleVector::GetAsciiHeader(std::string_view histogram_name, |
Quang Minh Tuan Nguyen | 39b1fed | 2021-03-16 00:49:38 | [diff] [blame] | 491 | int32_t flags) const { |
Ramon Cano Aparicio | b2cba0f | 2025-01-22 21:26:10 | [diff] [blame] | 492 | Count32 sample_count = TotalCount(); |
Quang Minh Tuan Nguyen | 39b1fed | 2021-03-16 00:49:38 | [diff] [blame] | 493 | std::string output; |
Greg Thompson | de48d06 | 2024-01-27 12:19:49 | [diff] [blame] | 494 | StrAppend(&output, {"Histogram: ", histogram_name, " recorded ", |
| 495 | NumberToString(sample_count), " samples"}); |
Quang Minh Tuan Nguyen | 39b1fed | 2021-03-16 00:49:38 | [diff] [blame] | 496 | 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 | } |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 502 | if (flags) { |
Quang Minh Tuan Nguyen | 39b1fed | 2021-03-16 00:49:38 | [diff] [blame] | 503 | StringAppendF(&output, " (flags = 0x%x)", flags); |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 504 | } |
Quang Minh Tuan Nguyen | 39b1fed | 2021-03-16 00:49:38 | [diff] [blame] | 505 | return output; |
| 506 | } |
| 507 | |
| 508 | std::string SampleVector::GetAsciiBody() const { |
Ramon Cano Aparicio | b2cba0f | 2025-01-22 21:26:10 | [diff] [blame] | 509 | Count32 sample_count = TotalCount(); |
Quang Minh Tuan Nguyen | 39b1fed | 2021-03-16 00:49:38 | [diff] [blame] | 510 | |
| 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; |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 518 | if (max_size > kLineLength) { |
Quang Minh Tuan Nguyen | 39b1fed | 2021-03-16 00:49:38 | [diff] [blame] | 519 | scaling_factor = kLineLength / max_size; |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 520 | } |
Quang Minh Tuan Nguyen | 39b1fed | 2021-03-16 00:49:38 | [diff] [blame] | 521 | |
Quang Minh Tuan Nguyen | 39b1fed | 2021-03-16 00:49:38 | [diff] [blame] | 522 | // 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; |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 528 | if (width > print_width) { |
Quang Minh Tuan Nguyen | 39b1fed | 2021-03-16 00:49:38 | [diff] [blame] | 529 | print_width = width; |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 530 | } |
Quang Minh Tuan Nguyen | 39b1fed | 2021-03-16 00:49:38 | [diff] [blame] | 531 | } |
| 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 Aparicio | b2cba0f | 2025-01-22 21:26:10 | [diff] [blame] | 539 | Count32 current = GetCountAtIndex(i); |
Quang Minh Tuan Nguyen | 39b1fed | 2021-03-16 00:49:38 | [diff] [blame] | 540 | remaining -= current; |
| 541 | std::string range = GetSimpleAsciiBucketRange(bucket_ranges()->range(i)); |
| 542 | output.append(range); |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 543 | for (size_t j = 0; range.size() + j < print_width + 1; ++j) { |
Quang Minh Tuan Nguyen | 39b1fed | 2021-03-16 00:49:38 | [diff] [blame] | 544 | output.push_back(' '); |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 545 | } |
Quang Minh Tuan Nguyen | 39b1fed | 2021-03-16 00:49:38 | [diff] [blame] | 546 | 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 Aparicio | b2cba0f | 2025-01-22 21:26:10 | [diff] [blame] | 553 | Count32 current_size = round(current * scaling_factor); |
Quang Minh Tuan Nguyen | 39b1fed | 2021-03-16 00:49:38 | [diff] [blame] | 554 | 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 | |
| 563 | double SampleVector::GetPeakBucketSize() const { |
Ramon Cano Aparicio | b2cba0f | 2025-01-22 21:26:10 | [diff] [blame] | 564 | Count32 max = 0; |
Quang Minh Tuan Nguyen | 39b1fed | 2021-03-16 00:49:38 | [diff] [blame] | 565 | for (uint32_t i = 0; i < bucket_count(); ++i) { |
Ramon Cano Aparicio | b2cba0f | 2025-01-22 21:26:10 | [diff] [blame] | 566 | Count32 current = GetCountAtIndex(i); |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 567 | if (current > max) { |
Quang Minh Tuan Nguyen | 39b1fed | 2021-03-16 00:49:38 | [diff] [blame] | 568 | max = current; |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 569 | } |
Quang Minh Tuan Nguyen | 39b1fed | 2021-03-16 00:49:38 | [diff] [blame] | 570 | } |
| 571 | return max; |
| 572 | } |
| 573 | |
| 574 | void SampleVector::WriteAsciiBucketContext(int64_t past, |
Ramon Cano Aparicio | b2cba0f | 2025-01-22 21:26:10 | [diff] [blame] | 575 | Count32 current, |
Quang Minh Tuan Nguyen | 39b1fed | 2021-03-16 00:49:38 | [diff] [blame] | 576 | 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 | |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 587 | span<HistogramBase::AtomicCount> |
| 588 | SampleVector::CreateCountsStorageWhileLocked() { |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 589 | local_counts_.resize(counts_size()); |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 590 | return local_counts_; |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 591 | } |
| 592 | |
| 593 | PersistentSampleVector::PersistentSampleVector( |
Roger McFarlane | d9048a0 | 2025-04-14 16:22:01 | [diff] [blame] | 594 | std::string_view name, |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 595 | 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 McFarlane | 58b747d69 | 2025-05-16 00:07:01 | [diff] [blame] | 613 | 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 McFarlane | d9048a0 | 2025-04-14 16:22:01 | [diff] [blame] | 635 | // TODO: crbug.com/410544723 - Remove crash keys and DumpWithoutCrashing |
| 636 | // once investigation is complete. |
Roger McFarlane | 58b747d69 | 2025-05-16 00:07:01 | [diff] [blame] | 637 | 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 McFarlane | d9048a0 | 2025-04-14 16:22:01 | [diff] [blame] | 643 | #endif |
Roger McFarlane | 58b747d69 | 2025-05-16 00:07:01 | [diff] [blame] | 644 | debug::DumpWithoutCrashing(); |
| 645 | } |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 646 | } |
| 647 | } |
| 648 | |
Chris Watkins | bb7211c | 2017-11-29 07:16:38 | [diff] [blame] | 649 | PersistentSampleVector::~PersistentSampleVector() = default; |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 650 | |
Luc Nguyen | bbaab29 | 2023-09-28 23:30:57 | [diff] [blame] | 651 | bool PersistentSampleVector::IsDefinitelyEmpty() const { |
| 652 | // Not implemented. |
Peter Boström | de57333 | 2024-08-26 20:42:45 | [diff] [blame] | 653 | NOTREACHED(); |
Luc Nguyen | bbaab29 | 2023-09-28 23:30:57 | [diff] [blame] | 654 | } |
| 655 | |
Roger McFarlane | 58b747d69 | 2025-05-16 00:07:01 | [diff] [blame] | 656 | // static |
| 657 | constinit std::atomic_uintptr_t |
| 658 | PersistentSampleVector::atomic_histogram_pointer{0}; |
| 659 | |
| 660 | // static |
| 661 | void PersistentSampleVector::ResetMountExistingCountsStorageResultForTesting() { |
| 662 | atomic_histogram_pointer.store(0, std::memory_order_release); |
| 663 | } |
| 664 | |
| 665 | // static |
| 666 | void 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 | |
| 688 | PersistentSampleVector::MountExistingCountsStorageResult |
| 689 | PersistentSampleVector::MountExistingCountsStorageImpl() const { |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 690 | // 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 |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 693 | // 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. |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 696 | |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 697 | if (!persistent_counts_.reference()) { |
Roger McFarlane | 58b747d69 | 2025-05-16 00:07:01 | [diff] [blame] | 698 | return MountExistingCountsStorageResult::kNothingToRead; |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 699 | } |
| 700 | |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 701 | // 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 McFarlane | 58b747d69 | 2025-05-16 00:07:01 | [diff] [blame] | 706 | return MountExistingCountsStorageResult::kCorrupt; |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 707 | } |
| 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 McFarlane | 58b747d69 | 2025-05-16 00:07:01 | [diff] [blame] | 711 | return MountExistingCountsStorageResult::kSucceeded; |
| 712 | } |
| 713 | |
| 714 | bool PersistentSampleVector::MountExistingCountsStorage() const { |
| 715 | return MountExistingCountsStorageImpl() == |
| 716 | MountExistingCountsStorageResult::kSucceeded; |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 717 | } |
| 718 | |
| 719 | span<HistogramBase::AtomicCount> |
| 720 | PersistentSampleVector::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 Svitkine | 2d87fb5a | 2024-03-04 16:49:38 | [diff] [blame] | 730 | ANNOTATE_LEAKING_OBJECT_PTR(array.data()); |
danakj | ad724a8 | 2024-01-25 17:37:40 | [diff] [blame] | 731 | 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()); |
bcwhite | fa8485b | 2017-05-01 16:43:25 | [diff] [blame] | 737 | } |
| 738 | |
[email protected] | 2f7d9cd | 2012-09-22 03:42:12 | [diff] [blame] | 739 | } // namespace base |