histogram.cc revision 201ade2fbba22bfb27ae029f4d23fca6ded109a0
1// Copyright (c) 2010 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// Histogram is an object that aggregates statistics, and can summarize them in
6// various forms, including ASCII graphical, HTML, and numerically (as a
7// vector of numbers corresponding to each of the aggregating buckets).
8// See header file for details and examples.
9
10#include "base/metrics/histogram.h"
11
12#include <math.h>
13
14#include <algorithm>
15#include <string>
16
17#include "base/lock.h"
18#include "base/logging.h"
19#include "base/pickle.h"
20#include "base/stringprintf.h"
21
22namespace base {
23
24typedef Histogram::Count Count;
25
26scoped_refptr<Histogram> Histogram::FactoryGet(const std::string& name,
27    Sample minimum, Sample maximum, size_t bucket_count, Flags flags) {
28  scoped_refptr<Histogram> histogram(NULL);
29
30  // Defensive code.
31  if (minimum < 1)
32    minimum = 1;
33  if (maximum > kSampleType_MAX - 1)
34    maximum = kSampleType_MAX - 1;
35
36  if (!StatisticsRecorder::FindHistogram(name, &histogram)) {
37    histogram = new Histogram(name, minimum, maximum, bucket_count);
38    StatisticsRecorder::FindHistogram(name, &histogram);
39  }
40
41  DCHECK_EQ(HISTOGRAM, histogram->histogram_type());
42  DCHECK(histogram->HasConstructorArguments(minimum, maximum, bucket_count));
43  histogram->SetFlags(flags);
44  return histogram;
45}
46
47scoped_refptr<Histogram> Histogram::FactoryTimeGet(const std::string& name,
48                                                   TimeDelta minimum,
49                                                   TimeDelta maximum,
50                                                   size_t bucket_count,
51                                                   Flags flags) {
52  return FactoryGet(name, minimum.InMilliseconds(), maximum.InMilliseconds(),
53                    bucket_count, flags);
54}
55
56Histogram::Histogram(const std::string& name, Sample minimum,
57                     Sample maximum, size_t bucket_count)
58  : histogram_name_(name),
59    declared_min_(minimum),
60    declared_max_(maximum),
61    bucket_count_(bucket_count),
62    flags_(kNoFlags),
63    ranges_(bucket_count + 1, 0),
64    range_checksum_(0),
65    sample_() {
66  Initialize();
67}
68
69Histogram::Histogram(const std::string& name, TimeDelta minimum,
70                     TimeDelta maximum, size_t bucket_count)
71  : histogram_name_(name),
72    declared_min_(static_cast<int> (minimum.InMilliseconds())),
73    declared_max_(static_cast<int> (maximum.InMilliseconds())),
74    bucket_count_(bucket_count),
75    flags_(kNoFlags),
76    ranges_(bucket_count + 1, 0),
77    range_checksum_(0),
78    sample_() {
79  Initialize();
80}
81
82Histogram::~Histogram() {
83  if (StatisticsRecorder::dump_on_exit()) {
84    std::string output;
85    WriteAscii(true, "\n", &output);
86    LOG(INFO) << output;
87  }
88
89  // Just to make sure most derived class did this properly...
90  DCHECK(ValidateBucketRanges());
91  DCHECK(HasValidRangeChecksum());
92}
93
94bool Histogram::PrintEmptyBucket(size_t index) const {
95  return true;
96}
97
98void Histogram::Add(int value) {
99  if (value > kSampleType_MAX - 1)
100    value = kSampleType_MAX - 1;
101  if (value < 0)
102    value = 0;
103  size_t index = BucketIndex(value);
104  DCHECK_GE(value, ranges(index));
105  DCHECK_LT(value, ranges(index + 1));
106  Accumulate(value, 1, index);
107}
108
109void Histogram::AddBoolean(bool value) {
110  DCHECK(false);
111}
112
113void Histogram::AddSampleSet(const SampleSet& sample) {
114  sample_.Add(sample);
115}
116
117void Histogram::SetRangeDescriptions(const DescriptionPair descriptions[]) {
118  DCHECK(false);
119}
120
121// The following methods provide a graphical histogram display.
122void Histogram::WriteHTMLGraph(std::string* output) const {
123  // TBD(jar) Write a nice HTML bar chart, with divs an mouse-overs etc.
124  output->append("<PRE>");
125  WriteAscii(true, "<br>", output);
126  output->append("</PRE>");
127}
128
129void Histogram::WriteAscii(bool graph_it, const std::string& newline,
130                           std::string* output) const {
131  // Get local (stack) copies of all effectively volatile class data so that we
132  // are consistent across our output activities.
133  SampleSet snapshot;
134  SnapshotSample(&snapshot);
135  Count sample_count = snapshot.TotalCount();
136
137  WriteAsciiHeader(snapshot, sample_count, output);
138  output->append(newline);
139
140  // Prepare to normalize graphical rendering of bucket contents.
141  double max_size = 0;
142  if (graph_it)
143    max_size = GetPeakBucketSize(snapshot);
144
145  // Calculate space needed to print bucket range numbers.  Leave room to print
146  // nearly the largest bucket range without sliding over the histogram.
147  size_t largest_non_empty_bucket = bucket_count() - 1;
148  while (0 == snapshot.counts(largest_non_empty_bucket)) {
149    if (0 == largest_non_empty_bucket)
150      break;  // All buckets are empty.
151    --largest_non_empty_bucket;
152  }
153
154  // Calculate largest print width needed for any of our bucket range displays.
155  size_t print_width = 1;
156  for (size_t i = 0; i < bucket_count(); ++i) {
157    if (snapshot.counts(i)) {
158      size_t width = GetAsciiBucketRange(i).size() + 1;
159      if (width > print_width)
160        print_width = width;
161    }
162  }
163
164  int64 remaining = sample_count;
165  int64 past = 0;
166  // Output the actual histogram graph.
167  for (size_t i = 0; i < bucket_count(); ++i) {
168    Count current = snapshot.counts(i);
169    if (!current && !PrintEmptyBucket(i))
170      continue;
171    remaining -= current;
172    std::string range = GetAsciiBucketRange(i);
173    output->append(range);
174    for (size_t j = 0; range.size() + j < print_width + 1; ++j)
175      output->push_back(' ');
176    if (0 == current && i < bucket_count() - 1 && 0 == snapshot.counts(i + 1)) {
177      while (i < bucket_count() - 1 && 0 == snapshot.counts(i + 1))
178        ++i;
179      output->append("... ");
180      output->append(newline);
181      continue;  // No reason to plot emptiness.
182    }
183    double current_size = GetBucketSize(current, i);
184    if (graph_it)
185      WriteAsciiBucketGraph(current_size, max_size, output);
186    WriteAsciiBucketContext(past, current, remaining, i, output);
187    output->append(newline);
188    past += current;
189  }
190  DCHECK_EQ(sample_count, past);
191}
192
193bool Histogram::ValidateBucketRanges() const {
194  // Standard assertions that all bucket ranges should satisfy.
195  DCHECK_EQ(bucket_count_ + 1, ranges_.size());
196  DCHECK_EQ(0, ranges_[0]);
197  DCHECK_EQ(declared_min(), ranges_[1]);
198  DCHECK_EQ(declared_max(), ranges_[bucket_count_ - 1]);
199  DCHECK_EQ(kSampleType_MAX, ranges_[bucket_count_]);
200  return true;
201}
202
203void Histogram::Initialize() {
204  sample_.Resize(*this);
205  if (declared_min_ < 1)
206    declared_min_ = 1;
207  if (declared_max_ > kSampleType_MAX - 1)
208    declared_max_ = kSampleType_MAX - 1;
209  DCHECK_LE(declared_min_, declared_max_);
210  DCHECK_GT(bucket_count_, 1u);
211  size_t maximal_bucket_count = declared_max_ - declared_min_ + 2;
212  DCHECK_LE(bucket_count_, maximal_bucket_count);
213  DCHECK_EQ(0, ranges_[0]);
214  ranges_[bucket_count_] = kSampleType_MAX;
215  InitializeBucketRange();
216  DCHECK(ValidateBucketRanges());
217  StatisticsRecorder::Register(this);
218}
219
220// Calculate what range of values are held in each bucket.
221// We have to be careful that we don't pick a ratio between starting points in
222// consecutive buckets that is sooo small, that the integer bounds are the same
223// (effectively making one bucket get no values).  We need to avoid:
224//   ranges_[i] == ranges_[i + 1]
225// To avoid that, we just do a fine-grained bucket width as far as we need to
226// until we get a ratio that moves us along at least 2 units at a time.  From
227// that bucket onward we do use the exponential growth of buckets.
228void Histogram::InitializeBucketRange() {
229  double log_max = log(static_cast<double>(declared_max()));
230  double log_ratio;
231  double log_next;
232  size_t bucket_index = 1;
233  Sample current = declared_min();
234  SetBucketRange(bucket_index, current);
235  while (bucket_count() > ++bucket_index) {
236    double log_current;
237    log_current = log(static_cast<double>(current));
238    // Calculate the count'th root of the range.
239    log_ratio = (log_max - log_current) / (bucket_count() - bucket_index);
240    // See where the next bucket would start.
241    log_next = log_current + log_ratio;
242    int next;
243    next = static_cast<int>(floor(exp(log_next) + 0.5));
244    if (next > current)
245      current = next;
246    else
247      ++current;  // Just do a narrow bucket, and keep trying.
248    SetBucketRange(bucket_index, current);
249  }
250  ResetRangeChecksum();
251
252  DCHECK_EQ(bucket_count(), bucket_index);
253}
254
255size_t Histogram::BucketIndex(Sample value) const {
256  // Use simple binary search.  This is very general, but there are better
257  // approaches if we knew that the buckets were linearly distributed.
258  DCHECK_LE(ranges(0), value);
259  DCHECK_GT(ranges(bucket_count()), value);
260  size_t under = 0;
261  size_t over = bucket_count();
262  size_t mid;
263
264  do {
265    DCHECK_GE(over, under);
266    mid = (over + under)/2;
267    if (mid == under)
268      break;
269    if (ranges(mid) <= value)
270      under = mid;
271    else
272      over = mid;
273  } while (true);
274
275  DCHECK_LE(ranges(mid), value);
276  DCHECK_GT(ranges(mid+1), value);
277  return mid;
278}
279
280// Use the actual bucket widths (like a linear histogram) until the widths get
281// over some transition value, and then use that transition width.  Exponentials
282// get so big so fast (and we don't expect to see a lot of entries in the large
283// buckets), so we need this to make it possible to see what is going on and
284// not have 0-graphical-height buckets.
285double Histogram::GetBucketSize(Count current, size_t i) const {
286  DCHECK_GT(ranges(i + 1), ranges(i));
287  static const double kTransitionWidth = 5;
288  double denominator = ranges(i + 1) - ranges(i);
289  if (denominator > kTransitionWidth)
290    denominator = kTransitionWidth;  // Stop trying to normalize.
291  return current/denominator;
292}
293
294void Histogram::ResetRangeChecksum() {
295  range_checksum_ = CalculateRangeChecksum();
296}
297
298bool Histogram::HasValidRangeChecksum() const {
299  return CalculateRangeChecksum() == range_checksum_;
300}
301
302Histogram::Sample Histogram::CalculateRangeChecksum() const {
303  DCHECK_EQ(ranges_.size(), bucket_count() + 1);
304  Sample checksum = 0;
305  for (size_t index = 0; index < bucket_count(); ++index) {
306    checksum += ranges(index);
307  }
308  return checksum;
309}
310
311//------------------------------------------------------------------------------
312// The following two methods can be overridden to provide a thread safe
313// version of this class.  The cost of locking is low... but an error in each
314// of these methods has minimal impact.  For now, I'll leave this unlocked,
315// and I don't believe I can loose more than a count or two.
316// The vectors are NOT reallocated, so there is no risk of them moving around.
317
318// Update histogram data with new sample.
319void Histogram::Accumulate(Sample value, Count count, size_t index) {
320  // Note locking not done in this version!!!
321  sample_.Accumulate(value, count, index);
322}
323
324// Do a safe atomic snapshot of sample data.
325// This implementation assumes we are on a safe single thread.
326void Histogram::SnapshotSample(SampleSet* sample) const {
327  // Note locking not done in this version!!!
328  *sample = sample_;
329}
330
331bool Histogram::HasConstructorArguments(Sample minimum,
332                                        Sample maximum,
333                                        size_t bucket_count) {
334  return ((minimum == declared_min_) && (maximum == declared_max_) &&
335          (bucket_count == bucket_count_));
336}
337
338bool Histogram::HasConstructorTimeDeltaArguments(TimeDelta minimum,
339                                                 TimeDelta maximum,
340                                                 size_t bucket_count) {
341  return ((minimum.InMilliseconds() == declared_min_) &&
342          (maximum.InMilliseconds() == declared_max_) &&
343          (bucket_count == bucket_count_));
344}
345
346//------------------------------------------------------------------------------
347// Accessor methods
348
349void Histogram::SetBucketRange(size_t i, Sample value) {
350  DCHECK_GT(bucket_count_, i);
351  ranges_[i] = value;
352}
353
354//------------------------------------------------------------------------------
355// Private methods
356
357double Histogram::GetPeakBucketSize(const SampleSet& snapshot) const {
358  double max = 0;
359  for (size_t i = 0; i < bucket_count() ; ++i) {
360    double current_size = GetBucketSize(snapshot.counts(i), i);
361    if (current_size > max)
362      max = current_size;
363  }
364  return max;
365}
366
367void Histogram::WriteAsciiHeader(const SampleSet& snapshot,
368                                 Count sample_count,
369                                 std::string* output) const {
370  StringAppendF(output,
371                "Histogram: %s recorded %d samples",
372                histogram_name().c_str(),
373                sample_count);
374  if (0 == sample_count) {
375    DCHECK_EQ(snapshot.sum(), 0);
376  } else {
377    double average = static_cast<float>(snapshot.sum()) / sample_count;
378    double variance = static_cast<float>(snapshot.square_sum())/sample_count
379                      - average * average;
380    double standard_deviation = sqrt(variance);
381
382    StringAppendF(output,
383                  ", average = %.1f, standard deviation = %.1f",
384                  average, standard_deviation);
385  }
386  if (flags_ & ~kHexRangePrintingFlag)
387    StringAppendF(output, " (flags = 0x%x)", flags_ & ~kHexRangePrintingFlag);
388}
389
390void Histogram::WriteAsciiBucketContext(const int64 past,
391                                        const Count current,
392                                        const int64 remaining,
393                                        const size_t i,
394                                        std::string* output) const {
395  double scaled_sum = (past + current + remaining) / 100.0;
396  WriteAsciiBucketValue(current, scaled_sum, output);
397  if (0 < i) {
398    double percentage = past / scaled_sum;
399    StringAppendF(output, " {%3.1f%%}", percentage);
400  }
401}
402
403const std::string Histogram::GetAsciiBucketRange(size_t i) const {
404  std::string result;
405  if (kHexRangePrintingFlag & flags_)
406    StringAppendF(&result, "%#x", ranges(i));
407  else
408    StringAppendF(&result, "%d", ranges(i));
409  return result;
410}
411
412void Histogram::WriteAsciiBucketValue(Count current, double scaled_sum,
413                                      std::string* output) const {
414  StringAppendF(output, " (%d = %3.1f%%)", current, current/scaled_sum);
415}
416
417void Histogram::WriteAsciiBucketGraph(double current_size, double max_size,
418                                      std::string* output) const {
419  const int k_line_length = 72;  // Maximal horizontal width of graph.
420  int x_count = static_cast<int>(k_line_length * (current_size / max_size)
421                                 + 0.5);
422  int x_remainder = k_line_length - x_count;
423
424  while (0 < x_count--)
425    output->append("-");
426  output->append("O");
427  while (0 < x_remainder--)
428    output->append(" ");
429}
430
431// static
432std::string Histogram::SerializeHistogramInfo(const Histogram& histogram,
433                                              const SampleSet& snapshot) {
434  DCHECK_NE(NOT_VALID_IN_RENDERER, histogram.histogram_type());
435
436  Pickle pickle;
437  pickle.WriteString(histogram.histogram_name());
438  pickle.WriteInt(histogram.declared_min());
439  pickle.WriteInt(histogram.declared_max());
440  pickle.WriteSize(histogram.bucket_count());
441  pickle.WriteInt(histogram.range_checksum());
442  pickle.WriteInt(histogram.histogram_type());
443  pickle.WriteInt(histogram.flags());
444
445  snapshot.Serialize(&pickle);
446  return std::string(static_cast<const char*>(pickle.data()), pickle.size());
447}
448
449// static
450bool Histogram::DeserializeHistogramInfo(const std::string& histogram_info) {
451  if (histogram_info.empty()) {
452      return false;
453  }
454
455  Pickle pickle(histogram_info.data(),
456                static_cast<int>(histogram_info.size()));
457  std::string histogram_name;
458  int declared_min;
459  int declared_max;
460  size_t bucket_count;
461  int range_checksum;
462  int histogram_type;
463  int pickle_flags;
464  SampleSet sample;
465
466  void* iter = NULL;
467  if (!pickle.ReadString(&iter, &histogram_name) ||
468      !pickle.ReadInt(&iter, &declared_min) ||
469      !pickle.ReadInt(&iter, &declared_max) ||
470      !pickle.ReadSize(&iter, &bucket_count) ||
471      !pickle.ReadInt(&iter, &range_checksum) ||
472      !pickle.ReadInt(&iter, &histogram_type) ||
473      !pickle.ReadInt(&iter, &pickle_flags) ||
474      !sample.Histogram::SampleSet::Deserialize(&iter, pickle)) {
475    LOG(ERROR) << "Pickle error decoding Histogram: " << histogram_name;
476    return false;
477  }
478  DCHECK(pickle_flags & kIPCSerializationSourceFlag);
479  // Since these fields may have come from an untrusted renderer, do additional
480  // checks above and beyond those in Histogram::Initialize()
481  if (declared_max <= 0 || declared_min <= 0 || declared_max < declared_min ||
482      INT_MAX / sizeof(Count) <= bucket_count || bucket_count < 2) {
483    LOG(ERROR) << "Values error decoding Histogram: " << histogram_name;
484    return false;
485  }
486
487  Flags flags = static_cast<Flags>(pickle_flags & ~kIPCSerializationSourceFlag);
488
489  DCHECK_NE(NOT_VALID_IN_RENDERER, histogram_type);
490
491  scoped_refptr<Histogram> render_histogram(NULL);
492
493  if (histogram_type == HISTOGRAM) {
494    render_histogram = Histogram::FactoryGet(
495        histogram_name, declared_min, declared_max, bucket_count, flags);
496  } else if (histogram_type == LINEAR_HISTOGRAM) {
497    render_histogram = LinearHistogram::FactoryGet(
498        histogram_name, declared_min, declared_max, bucket_count, flags);
499  } else if (histogram_type == BOOLEAN_HISTOGRAM) {
500    render_histogram = BooleanHistogram::FactoryGet(histogram_name, flags);
501  } else {
502    LOG(ERROR) << "Error Deserializing Histogram Unknown histogram_type: "
503               << histogram_type;
504    return false;
505  }
506
507  DCHECK_EQ(render_histogram->declared_min(), declared_min);
508  DCHECK_EQ(render_histogram->declared_max(), declared_max);
509  DCHECK_EQ(render_histogram->bucket_count(), bucket_count);
510  DCHECK_EQ(render_histogram->range_checksum(), range_checksum);
511  DCHECK_EQ(render_histogram->histogram_type(), histogram_type);
512
513  if (render_histogram->flags() & kIPCSerializationSourceFlag) {
514    DVLOG(1) << "Single process mode, histogram observed and not copied: "
515             << histogram_name;
516  } else {
517    DCHECK_EQ(flags & render_histogram->flags(), flags);
518    render_histogram->AddSampleSet(sample);
519  }
520
521  return true;
522}
523
524//------------------------------------------------------------------------------
525// Methods for the validating a sample and a related histogram.
526//------------------------------------------------------------------------------
527
528Histogram::Inconsistencies Histogram::FindCorruption(
529    const SampleSet& snapshot) const {
530  int inconsistencies = NO_INCONSISTENCIES;
531  Sample previous_range = -1;  // Bottom range is always 0.
532  Sample checksum = 0;
533  int64 count = 0;
534  for (size_t index = 0; index < bucket_count(); ++index) {
535    count += snapshot.counts(index);
536    int new_range = ranges(index);
537    checksum += new_range;
538    if (previous_range >= new_range)
539      inconsistencies |= BUCKET_ORDER_ERROR;
540    previous_range = new_range;
541  }
542
543  if (checksum != range_checksum_)
544    inconsistencies |= RANGE_CHECKSUM_ERROR;
545
546  int64 delta64 = snapshot.redundant_count() - count;
547  if (delta64 != 0) {
548    int delta = static_cast<int>(delta64);
549    if (delta != delta64)
550      delta = INT_MAX;  // Flag all giant errors as INT_MAX.
551    // Since snapshots of histograms are taken asynchronously relative to
552    // sampling (and snapped from different threads), it is pretty likely that
553    // we'll catch a redundant count that doesn't match the sample count.  We
554    // allow for a certain amount of slop before flagging this as an
555    // inconsistency.  Even with an inconsistency, we'll snapshot it again (for
556    // UMA in about a half hour, so we'll eventually get the data, if it was
557    // not the result of a corruption.  If histograms show that 1 is "too tight"
558    // then we may try to use 2 or 3 for this slop value.
559    const int kCommonRaceBasedCountMismatch = 1;
560    if (delta > 0) {
561      UMA_HISTOGRAM_COUNTS("Histogram.InconsistentCountHigh", delta);
562      if (delta > kCommonRaceBasedCountMismatch)
563        inconsistencies |= COUNT_HIGH_ERROR;
564    } else {
565      DCHECK_GT(0, delta);
566      UMA_HISTOGRAM_COUNTS("Histogram.InconsistentCountLow", -delta);
567      if (-delta > kCommonRaceBasedCountMismatch)
568        inconsistencies |= COUNT_LOW_ERROR;
569    }
570  }
571  return static_cast<Inconsistencies>(inconsistencies);
572}
573
574//------------------------------------------------------------------------------
575// Methods for the Histogram::SampleSet class
576//------------------------------------------------------------------------------
577
578Histogram::SampleSet::SampleSet()
579    : counts_(),
580      sum_(0),
581      square_sum_(0),
582      redundant_count_(0) {
583}
584
585Histogram::SampleSet::~SampleSet() {
586}
587
588void Histogram::SampleSet::Resize(const Histogram& histogram) {
589  counts_.resize(histogram.bucket_count(), 0);
590}
591
592void Histogram::SampleSet::CheckSize(const Histogram& histogram) const {
593  DCHECK_EQ(histogram.bucket_count(), counts_.size());
594}
595
596
597void Histogram::SampleSet::Accumulate(Sample value,  Count count,
598                                      size_t index) {
599  DCHECK(count == 1 || count == -1);
600  counts_[index] += count;
601  sum_ += count * value;
602  square_sum_ += (count * value) * static_cast<int64>(value);
603  redundant_count_ += count;
604  DCHECK_GE(counts_[index], 0);
605  DCHECK_GE(sum_, 0);
606  DCHECK_GE(square_sum_, 0);
607  DCHECK_GE(redundant_count_, 0);
608}
609
610Count Histogram::SampleSet::TotalCount() const {
611  Count total = 0;
612  for (Counts::const_iterator it = counts_.begin();
613       it != counts_.end();
614       ++it) {
615    total += *it;
616  }
617  return total;
618}
619
620void Histogram::SampleSet::Add(const SampleSet& other) {
621  DCHECK_EQ(counts_.size(), other.counts_.size());
622  sum_ += other.sum_;
623  square_sum_ += other.square_sum_;
624  redundant_count_ += other.redundant_count_;
625  for (size_t index = 0; index < counts_.size(); ++index)
626    counts_[index] += other.counts_[index];
627}
628
629void Histogram::SampleSet::Subtract(const SampleSet& other) {
630  DCHECK_EQ(counts_.size(), other.counts_.size());
631  // Note: Race conditions in snapshotting a sum or square_sum may lead to
632  // (temporary) negative values when snapshots are later combined (and deltas
633  // calculated).  As a result, we don't currently CHCEK() for positive values.
634  sum_ -= other.sum_;
635  square_sum_ -= other.square_sum_;
636  redundant_count_ -= other.redundant_count_;
637  for (size_t index = 0; index < counts_.size(); ++index) {
638    counts_[index] -= other.counts_[index];
639    DCHECK_GE(counts_[index], 0);
640  }
641}
642
643bool Histogram::SampleSet::Serialize(Pickle* pickle) const {
644  pickle->WriteInt64(sum_);
645  pickle->WriteInt64(square_sum_);
646  pickle->WriteInt64(redundant_count_);
647  pickle->WriteSize(counts_.size());
648
649  for (size_t index = 0; index < counts_.size(); ++index) {
650    pickle->WriteInt(counts_[index]);
651  }
652
653  return true;
654}
655
656bool Histogram::SampleSet::Deserialize(void** iter, const Pickle& pickle) {
657  DCHECK_EQ(counts_.size(), 0u);
658  DCHECK_EQ(sum_, 0);
659  DCHECK_EQ(square_sum_, 0);
660  DCHECK_EQ(redundant_count_, 0);
661
662  size_t counts_size;
663
664  if (!pickle.ReadInt64(iter, &sum_) ||
665      !pickle.ReadInt64(iter, &square_sum_) ||
666      !pickle.ReadInt64(iter, &redundant_count_) ||
667      !pickle.ReadSize(iter, &counts_size)) {
668    return false;
669  }
670
671  if (counts_size == 0)
672    return false;
673
674  int count = 0;
675  for (size_t index = 0; index < counts_size; ++index) {
676    int i;
677    if (!pickle.ReadInt(iter, &i))
678      return false;
679    counts_.push_back(i);
680    count += i;
681  }
682  DCHECK_EQ(count, redundant_count_);
683  return count == redundant_count_;
684}
685
686//------------------------------------------------------------------------------
687// LinearHistogram: This histogram uses a traditional set of evenly spaced
688// buckets.
689//------------------------------------------------------------------------------
690
691scoped_refptr<Histogram> LinearHistogram::FactoryGet(const std::string& name,
692                                                     Sample minimum,
693                                                     Sample maximum,
694                                                     size_t bucket_count,
695                                                     Flags flags) {
696  scoped_refptr<Histogram> histogram(NULL);
697
698  if (minimum < 1)
699    minimum = 1;
700  if (maximum > kSampleType_MAX - 1)
701    maximum = kSampleType_MAX - 1;
702
703  if (!StatisticsRecorder::FindHistogram(name, &histogram)) {
704    histogram = new LinearHistogram(name, minimum, maximum, bucket_count);
705    StatisticsRecorder::FindHistogram(name, &histogram);
706  }
707
708  DCHECK_EQ(LINEAR_HISTOGRAM, histogram->histogram_type());
709  DCHECK(histogram->HasConstructorArguments(minimum, maximum, bucket_count));
710  histogram->SetFlags(flags);
711  return histogram;
712}
713
714scoped_refptr<Histogram> LinearHistogram::FactoryTimeGet(
715    const std::string& name,
716    TimeDelta minimum,
717    TimeDelta maximum,
718    size_t bucket_count,
719    Flags flags) {
720  return FactoryGet(name, minimum.InMilliseconds(), maximum.InMilliseconds(),
721                    bucket_count, flags);
722}
723
724LinearHistogram::~LinearHistogram() {
725}
726
727LinearHistogram::LinearHistogram(const std::string& name,
728                                 Sample minimum,
729                                 Sample maximum,
730                                 size_t bucket_count)
731    : Histogram(name, minimum >= 1 ? minimum : 1, maximum, bucket_count) {
732  InitializeBucketRange();
733  DCHECK(ValidateBucketRanges());
734}
735
736LinearHistogram::LinearHistogram(const std::string& name,
737                                 TimeDelta minimum,
738                                 TimeDelta maximum,
739                                 size_t bucket_count)
740    : Histogram(name, minimum >= TimeDelta::FromMilliseconds(1) ?
741                                 minimum : TimeDelta::FromMilliseconds(1),
742                maximum, bucket_count) {
743  // Do a "better" (different) job at init than a base classes did...
744  InitializeBucketRange();
745  DCHECK(ValidateBucketRanges());
746}
747
748Histogram::ClassType LinearHistogram::histogram_type() const {
749  return LINEAR_HISTOGRAM;
750}
751
752void LinearHistogram::SetRangeDescriptions(
753    const DescriptionPair descriptions[]) {
754  for (int i =0; descriptions[i].description; ++i) {
755    bucket_description_[descriptions[i].sample] = descriptions[i].description;
756  }
757}
758
759const std::string LinearHistogram::GetAsciiBucketRange(size_t i) const {
760  int range = ranges(i);
761  BucketDescriptionMap::const_iterator it = bucket_description_.find(range);
762  if (it == bucket_description_.end())
763    return Histogram::GetAsciiBucketRange(i);
764  return it->second;
765}
766
767bool LinearHistogram::PrintEmptyBucket(size_t index) const {
768  return bucket_description_.find(ranges(index)) == bucket_description_.end();
769}
770
771
772void LinearHistogram::InitializeBucketRange() {
773  DCHECK_GT(declared_min(), 0);  // 0 is the underflow bucket here.
774  double min = declared_min();
775  double max = declared_max();
776  size_t i;
777  for (i = 1; i < bucket_count(); ++i) {
778    double linear_range = (min * (bucket_count() -1 - i) + max * (i - 1)) /
779                          (bucket_count() - 2);
780    SetBucketRange(i, static_cast<int> (linear_range + 0.5));
781  }
782  ResetRangeChecksum();
783}
784
785double LinearHistogram::GetBucketSize(Count current, size_t i) const {
786  DCHECK_GT(ranges(i + 1), ranges(i));
787  // Adjacent buckets with different widths would have "surprisingly" many (few)
788  // samples in a histogram if we didn't normalize this way.
789  double denominator = ranges(i + 1) - ranges(i);
790  return current/denominator;
791}
792
793//------------------------------------------------------------------------------
794// This section provides implementation for BooleanHistogram.
795//------------------------------------------------------------------------------
796
797scoped_refptr<Histogram> BooleanHistogram::FactoryGet(const std::string& name,
798                                                      Flags flags) {
799  scoped_refptr<Histogram> histogram(NULL);
800
801  if (!StatisticsRecorder::FindHistogram(name, &histogram)) {
802    histogram = new BooleanHistogram(name);
803    StatisticsRecorder::FindHistogram(name, &histogram);
804  }
805
806  DCHECK_EQ(BOOLEAN_HISTOGRAM, histogram->histogram_type());
807  histogram->SetFlags(flags);
808  return histogram;
809}
810
811Histogram::ClassType BooleanHistogram::histogram_type() const {
812  return BOOLEAN_HISTOGRAM;
813}
814
815void BooleanHistogram::AddBoolean(bool value) {
816  Add(value ? 1 : 0);
817}
818
819BooleanHistogram::BooleanHistogram(const std::string& name)
820    : LinearHistogram(name, 1, 2, 3) {
821}
822
823//------------------------------------------------------------------------------
824// CustomHistogram:
825//------------------------------------------------------------------------------
826
827scoped_refptr<Histogram> CustomHistogram::FactoryGet(
828    const std::string& name,
829    const std::vector<Sample>& custom_ranges,
830    Flags flags) {
831  scoped_refptr<Histogram> histogram(NULL);
832
833  // Remove the duplicates in the custom ranges array.
834  std::vector<int> ranges = custom_ranges;
835  ranges.push_back(0);  // Ensure we have a zero value.
836  std::sort(ranges.begin(), ranges.end());
837  ranges.erase(std::unique(ranges.begin(), ranges.end()), ranges.end());
838  if (ranges.size() <= 1) {
839    DCHECK(false);
840    // Note that we pushed a 0 in above, so for defensive code....
841    ranges.push_back(1);  // Put in some data so we can index to [1].
842  }
843
844  DCHECK_LT(ranges.back(), kSampleType_MAX);
845
846  if (!StatisticsRecorder::FindHistogram(name, &histogram)) {
847    histogram = new CustomHistogram(name, ranges);
848    StatisticsRecorder::FindHistogram(name, &histogram);
849  }
850
851  DCHECK_EQ(histogram->histogram_type(), CUSTOM_HISTOGRAM);
852  DCHECK(histogram->HasConstructorArguments(ranges[1], ranges.back(),
853                                            ranges.size()));
854  histogram->SetFlags(flags);
855  return histogram;
856}
857
858Histogram::ClassType CustomHistogram::histogram_type() const {
859  return CUSTOM_HISTOGRAM;
860}
861
862CustomHistogram::CustomHistogram(const std::string& name,
863                                 const std::vector<Sample>& custom_ranges)
864    : Histogram(name, custom_ranges[1], custom_ranges.back(),
865                custom_ranges.size()) {
866  DCHECK_GT(custom_ranges.size(), 1u);
867  DCHECK_EQ(custom_ranges[0], 0);
868  ranges_vector_ = &custom_ranges;
869  InitializeBucketRange();
870  ranges_vector_ = NULL;
871  DCHECK(ValidateBucketRanges());
872}
873
874void CustomHistogram::InitializeBucketRange() {
875  DCHECK_LE(ranges_vector_->size(), bucket_count());
876  for (size_t index = 0; index < ranges_vector_->size(); ++index)
877    SetBucketRange(index, (*ranges_vector_)[index]);
878  ResetRangeChecksum();
879}
880
881double CustomHistogram::GetBucketSize(Count current, size_t i) const {
882  return 1;
883}
884
885//------------------------------------------------------------------------------
886// The next section handles global (central) support for all histograms, as well
887// as startup/teardown of this service.
888//------------------------------------------------------------------------------
889
890// This singleton instance should be started during the single threaded portion
891// of main(), and hence it is not thread safe.  It initializes globals to
892// provide support for all future calls.
893StatisticsRecorder::StatisticsRecorder() {
894  DCHECK(!histograms_);
895  lock_ = new Lock;
896  histograms_ = new HistogramMap;
897}
898
899StatisticsRecorder::~StatisticsRecorder() {
900  DCHECK(histograms_);
901
902  if (dump_on_exit_) {
903    std::string output;
904    WriteGraph("", &output);
905    LOG(INFO) << output;
906  }
907  // Clean up.
908  delete histograms_;
909  histograms_ = NULL;
910  delete lock_;
911  lock_ = NULL;
912}
913
914// static
915bool StatisticsRecorder::WasStarted() {
916  return NULL != histograms_;
917}
918
919// Note: We can't accept a ref_ptr to |histogram| because we *might* not keep a
920// reference, and we are called while in the Histogram constructor. In that
921// scenario, a ref_ptr would have incremented the ref count when the histogram
922// was passed to us, decremented it when we returned, and the instance would be
923// destroyed before assignment (when value was returned by new).
924// static
925void StatisticsRecorder::Register(Histogram* histogram) {
926  if (!histograms_)
927    return;
928  const std::string name = histogram->histogram_name();
929  AutoLock auto_lock(*lock_);
930  // Avoid overwriting a previous registration.
931  if (histograms_->end() == histograms_->find(name))
932    (*histograms_)[name] = histogram;
933}
934
935// static
936void StatisticsRecorder::WriteHTMLGraph(const std::string& query,
937                                        std::string* output) {
938  if (!histograms_)
939    return;
940  output->append("<html><head><title>About Histograms");
941  if (!query.empty())
942    output->append(" - " + query);
943  output->append("</title>"
944                 // We'd like the following no-cache... but it doesn't work.
945                 // "<META HTTP-EQUIV=\"Pragma\" CONTENT=\"no-cache\">"
946                 "</head><body>");
947
948  Histograms snapshot;
949  GetSnapshot(query, &snapshot);
950  for (Histograms::iterator it = snapshot.begin();
951       it != snapshot.end();
952       ++it) {
953    (*it)->WriteHTMLGraph(output);
954    output->append("<br><hr><br>");
955  }
956  output->append("</body></html>");
957}
958
959// static
960void StatisticsRecorder::WriteGraph(const std::string& query,
961                                    std::string* output) {
962  if (!histograms_)
963    return;
964  if (query.length())
965    StringAppendF(output, "Collections of histograms for %s\n", query.c_str());
966  else
967    output->append("Collections of all histograms\n");
968
969  Histograms snapshot;
970  GetSnapshot(query, &snapshot);
971  for (Histograms::iterator it = snapshot.begin();
972       it != snapshot.end();
973       ++it) {
974    (*it)->WriteAscii(true, "\n", output);
975    output->append("\n");
976  }
977}
978
979// static
980void StatisticsRecorder::GetHistograms(Histograms* output) {
981  if (!histograms_)
982    return;
983  AutoLock auto_lock(*lock_);
984  for (HistogramMap::iterator it = histograms_->begin();
985       histograms_->end() != it;
986       ++it) {
987    DCHECK_EQ(it->first, it->second->histogram_name());
988    output->push_back(it->second);
989  }
990}
991
992bool StatisticsRecorder::FindHistogram(const std::string& name,
993                                       scoped_refptr<Histogram>* histogram) {
994  if (!histograms_)
995    return false;
996  AutoLock auto_lock(*lock_);
997  HistogramMap::iterator it = histograms_->find(name);
998  if (histograms_->end() == it)
999    return false;
1000  *histogram = it->second;
1001  return true;
1002}
1003
1004// private static
1005void StatisticsRecorder::GetSnapshot(const std::string& query,
1006                                     Histograms* snapshot) {
1007  AutoLock auto_lock(*lock_);
1008  for (HistogramMap::iterator it = histograms_->begin();
1009       histograms_->end() != it;
1010       ++it) {
1011    if (it->first.find(query) != std::string::npos)
1012      snapshot->push_back(it->second);
1013  }
1014}
1015
1016// static
1017StatisticsRecorder::HistogramMap* StatisticsRecorder::histograms_ = NULL;
1018// static
1019Lock* StatisticsRecorder::lock_ = NULL;
1020// static
1021bool StatisticsRecorder::dump_on_exit_ = false;
1022
1023}  // namespace base
1024