histogram_samples.cc 14 KB

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