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