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