1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "base/metrics/histogram_samples.h" 6 7 #include <limits> 8 9 #include "base/compiler_specific.h" 10 #include "base/metrics/histogram_functions.h" 11 #include "base/metrics/histogram_macros.h" 12 #include "base/numerics/safe_conversions.h" 13 #include "base/numerics/safe_math.h" 14 #include "base/pickle.h" 15 16 namespace base { 17 18 namespace { 19 20 // A shorthand constant for the max value of size_t. 21 constexpr size_t kSizeMax = std::numeric_limits<size_t>::max(); 22 23 // A constant stored in an AtomicSingleSample (as_atomic) to indicate that the 24 // sample is "disabled" and no further accumulation should be done with it. The 25 // value is chosen such that it will be MAX_UINT16 for both |bucket| & |count|, 26 // and thus less likely to conflict with real use. Conflicts are explicitly 27 // handled in the code but it's worth making them as unlikely as possible. 28 constexpr int32_t kDisabledSingleSample = -1; 29 30 class SampleCountPickleIterator : public SampleCountIterator { 31 public: 32 explicit SampleCountPickleIterator(PickleIterator* iter); 33 34 bool Done() const override; 35 void Next() override; 36 void Get(HistogramBase::Sample* min, 37 int64_t* max, 38 HistogramBase::Count* count) const override; 39 40 private: 41 PickleIterator* const iter_; 42 43 HistogramBase::Sample min_; 44 int64_t max_; 45 HistogramBase::Count count_; 46 bool is_done_; 47 }; 48 49 SampleCountPickleIterator::SampleCountPickleIterator(PickleIterator* iter) 50 : iter_(iter), 51 is_done_(false) { 52 Next(); 53 } 54 55 bool SampleCountPickleIterator::Done() const { 56 return is_done_; 57 } 58 59 void SampleCountPickleIterator::Next() { 60 DCHECK(!Done()); 61 if (!iter_->ReadInt(&min_) || !iter_->ReadInt64(&max_) || 62 !iter_->ReadInt(&count_)) { 63 is_done_ = true; 64 } 65 } 66 67 void SampleCountPickleIterator::Get(HistogramBase::Sample* min, 68 int64_t* max, 69 HistogramBase::Count* count) const { 70 DCHECK(!Done()); 71 *min = min_; 72 *max = max_; 73 *count = count_; 74 } 75 76 } // namespace 77 78 static_assert(sizeof(HistogramSamples::AtomicSingleSample) == 79 sizeof(subtle::Atomic32), 80 "AtomicSingleSample isn't 32 bits"); 81 82 HistogramSamples::SingleSample HistogramSamples::AtomicSingleSample::Load() 83 const { 84 AtomicSingleSample single_sample = subtle::Acquire_Load(&as_atomic); 85 86 // If the sample was extracted/disabled, it's still zero to the outside. 87 if (single_sample.as_atomic == kDisabledSingleSample) 88 single_sample.as_atomic = 0; 89 90 return single_sample.as_parts; 91 } 92 93 HistogramSamples::SingleSample HistogramSamples::AtomicSingleSample::Extract( 94 bool disable) { 95 AtomicSingleSample single_sample = subtle::NoBarrier_AtomicExchange( 96 &as_atomic, disable ? kDisabledSingleSample : 0); 97 if (single_sample.as_atomic == kDisabledSingleSample) 98 single_sample.as_atomic = 0; 99 return single_sample.as_parts; 100 } 101 102 bool HistogramSamples::AtomicSingleSample::Accumulate( 103 size_t bucket, 104 HistogramBase::Count count) { 105 if (count == 0) 106 return true; 107 108 // Convert the parameters to 16-bit variables because it's all 16-bit below. 109 // To support decrements/subtractions, divide the |count| into sign/value and 110 // do the proper operation below. The alternative is to change the single- 111 // sample's count to be a signed integer (int16_t) and just add an int16_t 112 // |count16| but that is somewhat wasteful given that the single-sample is 113 // never expected to have a count less than zero. 114 if (count < -std::numeric_limits<uint16_t>::max() || 115 count > std::numeric_limits<uint16_t>::max() || 116 bucket > std::numeric_limits<uint16_t>::max()) { 117 return false; 118 } 119 bool count_is_negative = count < 0; 120 uint16_t count16 = static_cast<uint16_t>(count_is_negative ? -count : count); 121 uint16_t bucket16 = static_cast<uint16_t>(bucket); 122 123 // A local, unshared copy of the single-sample is necessary so the parts 124 // can be manipulated without worrying about atomicity. 125 AtomicSingleSample single_sample; 126 127 bool sample_updated; 128 do { 129 subtle::Atomic32 original = subtle::Acquire_Load(&as_atomic); 130 if (original == kDisabledSingleSample) 131 return false; 132 single_sample.as_atomic = original; 133 if (single_sample.as_atomic != 0) { 134 // Only the same bucket (parameter and stored) can be counted multiple 135 // times. 136 if (single_sample.as_parts.bucket != bucket16) 137 return false; 138 } else { 139 // The |single_ sample| was zero so becomes the |bucket| parameter, the 140 // contents of which were checked above to fit in 16 bits. 141 single_sample.as_parts.bucket = bucket16; 142 } 143 144 // Update count, making sure that it doesn't overflow. 145 CheckedNumeric<uint16_t> new_count(single_sample.as_parts.count); 146 if (count_is_negative) 147 new_count -= count16; 148 else 149 new_count += count16; 150 if (!new_count.AssignIfValid(&single_sample.as_parts.count)) 151 return false; 152 153 // Don't let this become equivalent to the "disabled" value. 154 if (single_sample.as_atomic == kDisabledSingleSample) 155 return false; 156 157 // Store the updated single-sample back into memory. |existing| is what 158 // was in that memory location at the time of the call; if it doesn't 159 // match |original| then the swap didn't happen so loop again. 160 subtle::Atomic32 existing = subtle::Release_CompareAndSwap( 161 &as_atomic, original, single_sample.as_atomic); 162 sample_updated = (existing == original); 163 } while (!sample_updated); 164 165 return true; 166 } 167 168 bool HistogramSamples::AtomicSingleSample::IsDisabled() const { 169 return subtle::Acquire_Load(&as_atomic) == kDisabledSingleSample; 170 } 171 172 HistogramSamples::LocalMetadata::LocalMetadata() { 173 // This is the same way it's done for persistent metadata since no ctor 174 // is called for the data members in that case. 175 memset(this, 0, sizeof(*this)); 176 } 177 178 HistogramSamples::HistogramSamples(uint64_t id, Metadata* meta) 179 : meta_(meta) { 180 DCHECK(meta_->id == 0 || meta_->id == id); 181 182 // It's possible that |meta| is contained in initialized, read-only memory 183 // so it's essential that no write be done in that case. 184 if (!meta_->id) 185 meta_->id = id; 186 } 187 188 // This mustn't do anything with |meta_|. It was passed to the ctor and may 189 // be invalid by the time this dtor gets called. 190 HistogramSamples::~HistogramSamples() = default; 191 192 void HistogramSamples::Add(const HistogramSamples& other) { 193 IncreaseSumAndCount(other.sum(), other.redundant_count()); 194 std::unique_ptr<SampleCountIterator> it = other.Iterator(); 195 bool success = AddSubtractImpl(it.get(), ADD); 196 DCHECK(success); 197 } 198 199 bool HistogramSamples::AddFromPickle(PickleIterator* iter) { 200 int64_t sum; 201 HistogramBase::Count redundant_count; 202 203 if (!iter->ReadInt64(&sum) || !iter->ReadInt(&redundant_count)) 204 return false; 205 206 IncreaseSumAndCount(sum, redundant_count); 207 208 SampleCountPickleIterator pickle_iter(iter); 209 return AddSubtractImpl(&pickle_iter, ADD); 210 } 211 212 void HistogramSamples::Subtract(const HistogramSamples& other) { 213 IncreaseSumAndCount(-other.sum(), -other.redundant_count()); 214 std::unique_ptr<SampleCountIterator> it = other.Iterator(); 215 bool success = AddSubtractImpl(it.get(), SUBTRACT); 216 DCHECK(success); 217 } 218 219 void HistogramSamples::Serialize(Pickle* pickle) const { 220 pickle->WriteInt64(sum()); 221 pickle->WriteInt(redundant_count()); 222 223 HistogramBase::Sample min; 224 int64_t max; 225 HistogramBase::Count count; 226 for (std::unique_ptr<SampleCountIterator> it = Iterator(); !it->Done(); 227 it->Next()) { 228 it->Get(&min, &max, &count); 229 pickle->WriteInt(min); 230 pickle->WriteInt64(max); 231 pickle->WriteInt(count); 232 } 233 } 234 235 bool HistogramSamples::AccumulateSingleSample(HistogramBase::Sample value, 236 HistogramBase::Count count, 237 size_t bucket) { 238 if (single_sample().Accumulate(bucket, count)) { 239 // Success. Update the (separate) sum and redundant-count. 240 IncreaseSumAndCount(strict_cast<int64_t>(value) * count, count); 241 return true; 242 } 243 return false; 244 } 245 246 void HistogramSamples::IncreaseSumAndCount(int64_t sum, 247 HistogramBase::Count count) { 248 #ifdef ARCH_CPU_64_BITS 249 subtle::NoBarrier_AtomicIncrement(&meta_->sum, sum); 250 #else 251 meta_->sum += sum; 252 #endif 253 subtle::NoBarrier_AtomicIncrement(&meta_->redundant_count, count); 254 } 255 256 void HistogramSamples::RecordNegativeSample(NegativeSampleReason reason, 257 HistogramBase::Count increment) { 258 UMA_HISTOGRAM_ENUMERATION("UMA.NegativeSamples.Reason", reason, 259 MAX_NEGATIVE_SAMPLE_REASONS); 260 UMA_HISTOGRAM_CUSTOM_COUNTS("UMA.NegativeSamples.Increment", increment, 1, 261 1 << 30, 100); 262 UmaHistogramSparse("UMA.NegativeSamples.Histogram", 263 static_cast<int32_t>(id())); 264 } 265 266 SampleCountIterator::~SampleCountIterator() = default; 267 268 bool SampleCountIterator::GetBucketIndex(size_t* index) const { 269 DCHECK(!Done()); 270 return false; 271 } 272 273 SingleSampleIterator::SingleSampleIterator(HistogramBase::Sample min, 274 int64_t max, 275 HistogramBase::Count count) 276 : SingleSampleIterator(min, max, count, kSizeMax) {} 277 278 SingleSampleIterator::SingleSampleIterator(HistogramBase::Sample min, 279 int64_t max, 280 HistogramBase::Count count, 281 size_t bucket_index) 282 : min_(min), max_(max), bucket_index_(bucket_index), count_(count) {} 283 284 SingleSampleIterator::~SingleSampleIterator() = default; 285 286 bool SingleSampleIterator::Done() const { 287 return count_ == 0; 288 } 289 290 void SingleSampleIterator::Next() { 291 DCHECK(!Done()); 292 count_ = 0; 293 } 294 295 void SingleSampleIterator::Get(HistogramBase::Sample* min, 296 int64_t* max, 297 HistogramBase::Count* count) const { 298 DCHECK(!Done()); 299 if (min != nullptr) 300 *min = min_; 301 if (max != nullptr) 302 *max = max_; 303 if (count != nullptr) 304 *count = count_; 305 } 306 307 bool SingleSampleIterator::GetBucketIndex(size_t* index) const { 308 DCHECK(!Done()); 309 if (bucket_index_ == kSizeMax) 310 return false; 311 *index = bucket_index_; 312 return true; 313 } 314 315 } // namespace base 316