diff options
Diffstat (limited to 'base/metrics')
-rw-r--r-- | base/metrics/bucket_ranges.h | 2 | ||||
-rw-r--r-- | base/metrics/histogram.cc | 228 | ||||
-rw-r--r-- | base/metrics/histogram.h | 98 | ||||
-rw-r--r-- | base/metrics/histogram_flattener.h | 6 | ||||
-rw-r--r-- | base/metrics/histogram_samples.cc | 126 | ||||
-rw-r--r-- | base/metrics/histogram_samples.h | 89 | ||||
-rw-r--r-- | base/metrics/histogram_snapshot_manager.cc | 89 | ||||
-rw-r--r-- | base/metrics/histogram_snapshot_manager.h | 25 | ||||
-rw-r--r-- | base/metrics/histogram_unittest.cc | 80 | ||||
-rw-r--r-- | base/metrics/sample_vector.cc | 160 | ||||
-rw-r--r-- | base/metrics/sample_vector.h | 81 | ||||
-rw-r--r-- | base/metrics/sample_vector_unittest.cc | 265 |
12 files changed, 899 insertions, 350 deletions
diff --git a/base/metrics/bucket_ranges.h b/base/metrics/bucket_ranges.h index b77f52e..90b9047 100644 --- a/base/metrics/bucket_ranges.h +++ b/base/metrics/bucket_ranges.h @@ -26,7 +26,7 @@ namespace base { -class BASE_EXPORT_PRIVATE BucketRanges { +class BASE_EXPORT BucketRanges { public: typedef std::vector<HistogramBase::Sample> Ranges; diff --git a/base/metrics/histogram.cc b/base/metrics/histogram.cc index dbc76ef..9e490a0 100644 --- a/base/metrics/histogram.cc +++ b/base/metrics/histogram.cc @@ -34,104 +34,6 @@ typedef HistogramBase::Sample Sample; // static const size_t Histogram::kBucketCount_MAX = 16384u; -Histogram::SampleSet::SampleSet(size_t size) - : counts_(size, 0), - sum_(0), - redundant_count_(0) {} - -Histogram::SampleSet::SampleSet() - : counts_(), - sum_(0), - redundant_count_(0) {} - -Histogram::SampleSet::~SampleSet() {} - -void Histogram::SampleSet::Resize(size_t size) { - counts_.resize(size, 0); -} - -void Histogram::SampleSet::Accumulate(Sample value, Count count, - size_t index) { - DCHECK(count == 1 || count == -1); - counts_[index] += count; - sum_ += count * value; - redundant_count_ += count; - DCHECK_GE(counts_[index], 0); - DCHECK_GE(sum_, 0); - DCHECK_GE(redundant_count_, 0); -} - -Count Histogram::SampleSet::TotalCount() const { - Count total = 0; - for (Counts::const_iterator it = counts_.begin(); - it != counts_.end(); - ++it) { - total += *it; - } - return total; -} - -void Histogram::SampleSet::Add(const SampleSet& other) { - DCHECK_EQ(counts_.size(), other.counts_.size()); - sum_ += other.sum_; - redundant_count_ += other.redundant_count_; - for (size_t index = 0; index < counts_.size(); ++index) - counts_[index] += other.counts_[index]; -} - -void Histogram::SampleSet::Subtract(const SampleSet& other) { - DCHECK_EQ(counts_.size(), other.counts_.size()); - // Note: Race conditions in snapshotting a sum may lead to (temporary) - // negative values when snapshots are later combined (and deltas calculated). - // As a result, we don't currently CHCEK() for positive values. - sum_ -= other.sum_; - redundant_count_ -= other.redundant_count_; - for (size_t index = 0; index < counts_.size(); ++index) { - counts_[index] -= other.counts_[index]; - DCHECK_GE(counts_[index], 0); - } -} - -bool Histogram::SampleSet::Serialize(Pickle* pickle) const { - pickle->WriteInt64(sum_); - pickle->WriteInt64(redundant_count_); - pickle->WriteUInt64(counts_.size()); - - for (size_t index = 0; index < counts_.size(); ++index) { - pickle->WriteInt(counts_[index]); - } - - return true; -} - -bool Histogram::SampleSet::Deserialize(PickleIterator* iter) { - DCHECK_EQ(counts_.size(), 0u); - DCHECK_EQ(sum_, 0); - DCHECK_EQ(redundant_count_, 0); - - uint64 counts_size; - - if (!iter->ReadInt64(&sum_) || - !iter->ReadInt64(&redundant_count_) || - !iter->ReadUInt64(&counts_size)) { - return false; - } - - if (counts_size == 0) - return false; - - int count = 0; - for (uint64 index = 0; index < counts_size; ++index) { - int i; - if (!iter->ReadInt(&i)) - return false; - counts_.push_back(i); - count += i; - } - DCHECK_EQ(count, redundant_count_); - return count == redundant_count_; -} - // TODO(rtenneti): delete this code after debugging. void CheckCorruption(const Histogram& histogram, bool new_histogram) { const std::string& histogram_name = histogram.histogram_name(); @@ -245,24 +147,27 @@ void Histogram::InitializeBucketRanges(Sample minimum, ranges->ResetChecksum(); } -// static void Histogram::Add(int value) { + DCHECK_EQ(0, ranges(0)); + DCHECK_EQ(kSampleType_MAX, ranges(bucket_count_)); + if (value > kSampleType_MAX - 1) value = kSampleType_MAX - 1; if (value < 0) value = 0; - size_t index = BucketIndex(value); - DCHECK_GE(value, ranges(index)); - DCHECK_LT(value, ranges(index + 1)); - Accumulate(value, 1, index); + samples_->Accumulate(value, 1); } void Histogram::AddBoolean(bool value) { DCHECK(false); } -void Histogram::AddSampleSet(const SampleSet& sample) { - sample_.Add(sample); +void Histogram::AddSamples(const HistogramSamples& samples) { + samples_->Add(samples); +} + +bool Histogram::AddSamplesFromPickle(PickleIterator* iter) { + return samples_->AddFromPickle(iter); } void Histogram::SetRangeDescriptions(const DescriptionPair descriptions[]) { @@ -283,7 +188,7 @@ void Histogram::WriteAscii(string* output) const { // static string Histogram::SerializeHistogramInfo(const Histogram& histogram, - const SampleSet& snapshot) { + const HistogramSamples& snapshot) { DCHECK_NE(NOT_VALID_IN_RENDERER, histogram.histogram_type()); DCHECK(histogram.bucket_ranges()->HasValidChecksum()); @@ -296,10 +201,10 @@ string Histogram::SerializeHistogramInfo(const Histogram& histogram, pickle.WriteInt(histogram.histogram_type()); pickle.WriteInt(histogram.flags()); - snapshot.Serialize(&pickle); - histogram.SerializeRanges(&pickle); + snapshot.Serialize(&pickle); + return string(static_cast<const char*>(pickle.data()), pickle.size()); } @@ -318,7 +223,6 @@ bool Histogram::DeserializeHistogramInfo(const string& histogram_info) { uint32 range_checksum; int histogram_type; int pickle_flags; - SampleSet sample; PickleIterator iter(pickle); if (!iter.ReadString(&histogram_name) || @@ -327,8 +231,7 @@ bool Histogram::DeserializeHistogramInfo(const string& histogram_info) { !iter.ReadUInt64(&bucket_count) || !iter.ReadUInt32(&range_checksum) || !iter.ReadInt(&histogram_type) || - !iter.ReadInt(&pickle_flags) || - !sample.Histogram::SampleSet::Deserialize(&iter)) { + !iter.ReadInt(&pickle_flags)) { DLOG(ERROR) << "Pickle error decoding Histogram: " << histogram_name; return false; } @@ -382,23 +285,21 @@ bool Histogram::DeserializeHistogramInfo(const string& histogram_info) { if (render_histogram->flags() & kIPCSerializationSourceFlag) { DVLOG(1) << "Single process mode, histogram observed and not copied: " << histogram_name; - } else { - DCHECK_EQ(flags & render_histogram->flags(), flags); - render_histogram->AddSampleSet(sample); + return true; } - return true; + DCHECK_EQ(flags & render_histogram->flags(), flags); + return render_histogram->AddSamplesFromPickle(&iter); } +// static +const int Histogram::kCommonRaceBasedCountMismatch = 5; -// Validate a sample and related histogram. Histogram::Inconsistencies Histogram::FindCorruption( - const SampleSet& snapshot) const { + const HistogramSamples& samples) const { int inconsistencies = NO_INCONSISTENCIES; Sample previous_range = -1; // Bottom range is always 0. - int64 count = 0; for (size_t index = 0; index < bucket_count(); ++index) { - count += snapshot.counts(index); int new_range = ranges(index); if (previous_range >= new_range) inconsistencies |= BUCKET_ORDER_ERROR; @@ -408,20 +309,11 @@ Histogram::Inconsistencies Histogram::FindCorruption( if (!bucket_ranges()->HasValidChecksum()) inconsistencies |= RANGE_CHECKSUM_ERROR; - int64 delta64 = snapshot.redundant_count() - count; + int64 delta64 = samples.redundant_count() - samples.TotalCount(); if (delta64 != 0) { int delta = static_cast<int>(delta64); if (delta != delta64) delta = INT_MAX; // Flag all giant errors as INT_MAX. - // Since snapshots of histograms are taken asynchronously relative to - // sampling (and snapped from different threads), it is pretty likely that - // we'll catch a redundant count that doesn't match the sample count. We - // allow for a certain amount of slop before flagging this as an - // inconsistency. Even with an inconsistency, we'll snapshot it again (for - // UMA in about a half hour, so we'll eventually get the data, if it was - // not the result of a corruption. If histograms show that 1 is "too tight" - // then we may try to use 2 or 3 for this slop value. - const int kCommonRaceBasedCountMismatch = 5; if (delta > 0) { UMA_HISTOGRAM_COUNTS("Histogram.InconsistentCountHigh", delta); if (delta > kCommonRaceBasedCountMismatch) @@ -448,11 +340,10 @@ size_t Histogram::bucket_count() const { return bucket_count_; } -// Do a safe atomic snapshot of sample data. -// This implementation assumes we are on a safe single thread. -void Histogram::SnapshotSample(SampleSet* sample) const { - // Note locking not done in this version!!! - *sample = sample_; +scoped_ptr<SampleVector> Histogram::SnapshotSamples() const { + scoped_ptr<SampleVector> samples(new SampleVector(bucket_ranges())); + samples->Add(*samples_); + return samples.Pass(); } bool Histogram::HasConstructionArguments(Sample minimum, @@ -471,8 +362,10 @@ Histogram::Histogram(const string& name, bucket_ranges_(ranges), declared_min_(minimum), declared_max_(maximum), - bucket_count_(bucket_count), - sample_(bucket_count) {} + bucket_count_(bucket_count) { + if (ranges) + samples_.reset(new SampleVector(ranges)); +} Histogram::~Histogram() { if (StatisticsRecorder::dump_on_exit()) { @@ -519,31 +412,6 @@ bool Histogram::PrintEmptyBucket(size_t index) const { return true; } -size_t Histogram::BucketIndex(Sample value) const { - // Use simple binary search. This is very general, but there are better - // approaches if we knew that the buckets were linearly distributed. - DCHECK_LE(ranges(0), value); - DCHECK_GT(ranges(bucket_count()), value); - size_t under = 0; - size_t over = bucket_count(); - size_t mid; - - do { - DCHECK_GE(over, under); - mid = under + (over - under)/2; - if (mid == under) - break; - if (ranges(mid) <= value) - under = mid; - else - over = mid; - } while (true); - - DCHECK_LE(ranges(mid), value); - CHECK_GT(ranges(mid+1), value); - return mid; -} - // Use the actual bucket widths (like a linear histogram) until the widths get // over some transition value, and then use that transition width. Exponentials // get so big so fast (and we don't expect to see a lot of entries in the large @@ -567,12 +435,6 @@ const string Histogram::GetAsciiBucketRange(size_t i) const { return result; } -// Update histogram data with new sample. -void Histogram::Accumulate(Sample value, Count count, size_t index) { - // Note locking not done in this version!!! - sample_.Accumulate(value, count, index); -} - //------------------------------------------------------------------------------ // Private methods @@ -581,22 +443,21 @@ void Histogram::WriteAsciiImpl(bool graph_it, string* output) const { // Get local (stack) copies of all effectively volatile class data so that we // are consistent across our output activities. - SampleSet snapshot; - SnapshotSample(&snapshot); - Count sample_count = snapshot.TotalCount(); + scoped_ptr<SampleVector> snapshot = SnapshotSamples(); + Count sample_count = snapshot->TotalCount(); - WriteAsciiHeader(snapshot, sample_count, output); + WriteAsciiHeader(*snapshot, sample_count, output); output->append(newline); // Prepare to normalize graphical rendering of bucket contents. double max_size = 0; if (graph_it) - max_size = GetPeakBucketSize(snapshot); + max_size = GetPeakBucketSize(*snapshot); // Calculate space needed to print bucket range numbers. Leave room to print // nearly the largest bucket range without sliding over the histogram. size_t largest_non_empty_bucket = bucket_count() - 1; - while (0 == snapshot.counts(largest_non_empty_bucket)) { + while (0 == snapshot->GetCountAtIndex(largest_non_empty_bucket)) { if (0 == largest_non_empty_bucket) break; // All buckets are empty. --largest_non_empty_bucket; @@ -605,7 +466,7 @@ void Histogram::WriteAsciiImpl(bool graph_it, // Calculate largest print width needed for any of our bucket range displays. size_t print_width = 1; for (size_t i = 0; i < bucket_count(); ++i) { - if (snapshot.counts(i)) { + if (snapshot->GetCountAtIndex(i)) { size_t width = GetAsciiBucketRange(i).size() + 1; if (width > print_width) print_width = width; @@ -616,7 +477,7 @@ void Histogram::WriteAsciiImpl(bool graph_it, int64 past = 0; // Output the actual histogram graph. for (size_t i = 0; i < bucket_count(); ++i) { - Count current = snapshot.counts(i); + Count current = snapshot->GetCountAtIndex(i); if (!current && !PrintEmptyBucket(i)) continue; remaining -= current; @@ -624,9 +485,12 @@ void Histogram::WriteAsciiImpl(bool graph_it, output->append(range); for (size_t j = 0; range.size() + j < print_width + 1; ++j) output->push_back(' '); - if (0 == current && i < bucket_count() - 1 && 0 == snapshot.counts(i + 1)) { - while (i < bucket_count() - 1 && 0 == snapshot.counts(i + 1)) + if (0 == current && i < bucket_count() - 1 && + 0 == snapshot->GetCountAtIndex(i + 1)) { + while (i < bucket_count() - 1 && + 0 == snapshot->GetCountAtIndex(i + 1)) { ++i; + } output->append("... "); output->append(newline); continue; // No reason to plot emptiness. @@ -641,17 +505,17 @@ void Histogram::WriteAsciiImpl(bool graph_it, DCHECK_EQ(sample_count, past); } -double Histogram::GetPeakBucketSize(const SampleSet& snapshot) const { +double Histogram::GetPeakBucketSize(const SampleVector& samples) const { double max = 0; for (size_t i = 0; i < bucket_count() ; ++i) { - double current_size = GetBucketSize(snapshot.counts(i), i); + double current_size = GetBucketSize(samples.GetCountAtIndex(i), i); if (current_size > max) max = current_size; } return max; } -void Histogram::WriteAsciiHeader(const SampleSet& snapshot, +void Histogram::WriteAsciiHeader(const SampleVector& samples, Count sample_count, string* output) const { StringAppendF(output, @@ -659,9 +523,9 @@ void Histogram::WriteAsciiHeader(const SampleSet& snapshot, histogram_name().c_str(), sample_count); if (0 == sample_count) { - DCHECK_EQ(snapshot.sum(), 0); + DCHECK_EQ(samples.sum(), 0); } else { - double average = static_cast<float>(snapshot.sum()) / sample_count; + double average = static_cast<float>(samples.sum()) / sample_count; StringAppendF(output, ", average = %.1f", average); } diff --git a/base/metrics/histogram.h b/base/metrics/histogram.h index 6320db6..d86a397 100644 --- a/base/metrics/histogram.h +++ b/base/metrics/histogram.h @@ -68,8 +68,11 @@ #include "base/compiler_specific.h" #include "base/gtest_prod_util.h" #include "base/logging.h" +#include "base/memory/scoped_ptr.h" #include "base/metrics/bucket_ranges.h" #include "base/metrics/histogram_base.h" +#include "base/metrics/histogram_samples.h" +#include "base/metrics/sample_vector.h" #include "base/time.h" class Pickle; @@ -338,8 +341,10 @@ class Lock; //------------------------------------------------------------------------------ -class BooleanHistogram; class BucketRanges; +class SampleVector; + +class BooleanHistogram; class CustomHistogram; class Histogram; class LinearHistogram; @@ -383,57 +388,6 @@ class BASE_EXPORT Histogram : public HistogramBase { }; //---------------------------------------------------------------------------- - // Statistic values, developed over the life of the histogram. - - class BASE_EXPORT SampleSet { - public: - explicit SampleSet(size_t size); - SampleSet(); - ~SampleSet(); - - void Resize(size_t size); - - // Accessor for histogram to make routine additions. - void Accumulate(Sample value, Count count, size_t index); - - // Accessor methods. - size_t size() const { return counts_.size(); } - Count counts(size_t i) const { return counts_[i]; } - Count TotalCount() const; - int64 sum() const { return sum_; } - int64 redundant_count() const { return redundant_count_; } - - // Arithmetic manipulation of corresponding elements of the set. - void Add(const SampleSet& other); - void Subtract(const SampleSet& other); - - bool Serialize(Pickle* pickle) const; - bool Deserialize(PickleIterator* iter); - - protected: - // Actual histogram data is stored in buckets, showing the count of values - // that fit into each bucket. - Counts counts_; - - // Save simple stats locally. Note that this MIGHT get done in base class - // without shared memory at some point. - int64 sum_; // sum of samples. - - private: - // Allow tests to corrupt our innards for testing purposes. - FRIEND_TEST_ALL_PREFIXES(HistogramTest, CorruptSampleCounts); - - // To help identify memory corruption, we reduntantly save the number of - // samples we've accumulated into all of our buckets. We can compare this - // count to the sum of the counts in all buckets, and detect problems. Note - // that due to races in histogram accumulation (if a histogram is indeed - // updated on several threads simultaneously), the tallies might mismatch, - // and also the snapshotting code may asynchronously get a mismatch (though - // generally either race based mismatch cause is VERY rare). - int64 redundant_count_; - }; - - //---------------------------------------------------------------------------- // For a valid histogram, input should follow these restrictions: // minimum > 0 (if a minimum below 1 is specified, it will implicitly be // normalized up to 1) @@ -473,7 +427,8 @@ class BASE_EXPORT Histogram : public HistogramBase { Add(static_cast<int>(time.InMilliseconds())); } - void AddSampleSet(const SampleSet& sample); + void AddSamples(const HistogramSamples& samples); + bool AddSamplesFromPickle(PickleIterator* iter); // This method is an interface, used only by LinearHistogram. virtual void SetRangeDescriptions(const DescriptionPair descriptions[]); @@ -491,19 +446,28 @@ class BASE_EXPORT Histogram : public HistogramBase { // Serialize the given snapshot of a Histogram into a String. Uses // Pickle class to flatten the object. static std::string SerializeHistogramInfo(const Histogram& histogram, - const SampleSet& snapshot); + const HistogramSamples& snapshot); // The following method accepts a list of pickled histograms and // builds a histogram and updates shadow copy of histogram data in the // browser process. static bool DeserializeHistogramInfo(const std::string& histogram_info); + // This constant if for FindCorruption. Since snapshots of histograms are + // taken asynchronously relative to sampling, and our counting code currently + // does not prevent race conditions, it is pretty likely that we'll catch a + // redundant count that doesn't match the sample count. We allow for a + // certain amount of slop before flagging this as an inconsistency. Even with + // an inconsistency, we'll snapshot it again (for UMA in about a half hour), + // so we'll eventually get the data, if it was not the result of a corruption. + static const int kCommonRaceBasedCountMismatch; + // Check to see if bucket ranges, counts and tallies in the snapshot are // consistent with the bucket ranges and checksums in our histogram. This can // produce a false-alarm if a race occurred in the reading of the data during // a SnapShot process, but should otherwise be false at all times (unless we // have memory over-writes, or DRAM failures). - virtual Inconsistencies FindCorruption(const SampleSet& snapshot) const; + virtual Inconsistencies FindCorruption(const HistogramSamples& samples) const; //---------------------------------------------------------------------------- // Accessors for factory constuction, serialization and testing. @@ -517,7 +481,7 @@ class BASE_EXPORT Histogram : public HistogramBase { // Snapshot the current complete set of sample data. // Override with atomic/locked snapshot if needed. - virtual void SnapshotSample(SampleSet* sample) const; + virtual scoped_ptr<SampleVector> SnapshotSamples() const; virtual bool HasConstructionArguments(Sample minimum, Sample maximum, @@ -553,11 +517,6 @@ class BASE_EXPORT Histogram : public HistogramBase { // Method to override to skip the display of the i'th bucket if it's empty. virtual bool PrintEmptyBucket(size_t index) const; - //---------------------------------------------------------------------------- - // Methods to override to create histogram with different bucket widths. - //---------------------------------------------------------------------------- - // Find bucket to increment for sample value. - virtual size_t BucketIndex(Sample value) const; // Get normalized size, relative to the ranges(i). virtual double GetBucketSize(Count current, size_t i) const; @@ -566,12 +525,6 @@ class BASE_EXPORT Histogram : public HistogramBase { // be a name (or string description) given to the bucket. virtual const std::string GetAsciiBucketRange(size_t it) const; - //---------------------------------------------------------------------------- - // Methods to override to create thread safe histogram. - //---------------------------------------------------------------------------- - // Update all our internal data, including histogram - virtual void Accumulate(Sample value, Count count, size_t index); - private: // Allow tests to corrupt our innards for testing purposes. FRIEND_TEST_ALL_PREFIXES(HistogramTest, CorruptBucketBounds); @@ -589,12 +542,13 @@ class BASE_EXPORT Histogram : public HistogramBase { const std::string& newline, std::string* output) const; - // Find out how large the (graphically) the largest bucket will appear to be. - double GetPeakBucketSize(const SampleSet& snapshot) const; + // Find out how large (graphically) the largest bucket will appear to be. + double GetPeakBucketSize(const SampleVector& samples) const; // Write a common header message describing this histogram. - void WriteAsciiHeader(const SampleSet& snapshot, - Count sample_count, std::string* output) const; + void WriteAsciiHeader(const SampleVector& samples, + Count sample_count, + std::string* output) const; // Write information about previous, current, and next buckets. // Information such as cumulative percentage, etc. @@ -620,7 +574,7 @@ class BASE_EXPORT Histogram : public HistogramBase { // Finally, provide the state that changes with the addition of each new // sample. - SampleSet sample_; + scoped_ptr<SampleVector> samples_; DISALLOW_COPY_AND_ASSIGN(Histogram); }; diff --git a/base/metrics/histogram_flattener.h b/base/metrics/histogram_flattener.h index 01a49fc..6dd169e 100644 --- a/base/metrics/histogram_flattener.h +++ b/base/metrics/histogram_flattener.h @@ -14,14 +14,16 @@ namespace base { +class HistogramSamples; + // HistogramFlattener is an interface used by HistogramSnapshotManager, which // handles the logistics of gathering up available histograms for recording. // The implementors handle the exact lower level recording mechanism, or // error report mechanism. class BASE_EXPORT HistogramFlattener { public: - virtual void RecordDelta(const base::Histogram& histogram, - const base::Histogram::SampleSet& snapshot) = 0; + virtual void RecordDelta(const Histogram& histogram, + const HistogramSamples& snapshot) = 0; // Will be called each time a type of Inconsistenies is seen on a histogram, // during inspections done internally in HistogramSnapshotManager class. diff --git a/base/metrics/histogram_samples.cc b/base/metrics/histogram_samples.cc new file mode 100644 index 0000000..e375952 --- /dev/null +++ b/base/metrics/histogram_samples.cc @@ -0,0 +1,126 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/metrics/histogram_samples.h" + +#include "base/compiler_specific.h" +#include "base/pickle.h" + +namespace base { + +namespace { + +class SampleCountPickleIterator : public SampleCountIterator { + public: + SampleCountPickleIterator(PickleIterator* iter); + + virtual bool Done() const OVERRIDE; + virtual void Next() OVERRIDE; + virtual void Get(HistogramBase::Sample* min, + HistogramBase::Sample* max, + HistogramBase::Count* count) const OVERRIDE; + private: + PickleIterator* const iter_; + + HistogramBase::Sample min_; + HistogramBase::Sample max_; + HistogramBase::Count count_; + bool is_done_; +}; + +SampleCountPickleIterator::SampleCountPickleIterator(PickleIterator* iter) + : iter_(iter), + is_done_(false) { + Next(); +} + +bool SampleCountPickleIterator::Done() const { + return is_done_; +} + +void SampleCountPickleIterator::Next() { + DCHECK(!Done()); + if (!iter_->ReadInt(&min_) || + !iter_->ReadInt(&max_) || + !iter_->ReadInt(&count_)) + is_done_ = true; +} + +void SampleCountPickleIterator::Get(HistogramBase::Sample* min, + HistogramBase::Sample* max, + HistogramBase::Count* count) const { + DCHECK(!Done()); + *min = min_; + *max = max_; + *count = count_; +} + +} // namespace + +HistogramSamples::HistogramSamples() : sum_(0), redundant_count_(0) {} + +HistogramSamples::~HistogramSamples() {} + +void HistogramSamples::Add(const HistogramSamples& other) { + sum_ += other.sum(); + redundant_count_ += other.redundant_count(); + bool success = AddSubtractImpl(other.Iterator().get(), ADD); + DCHECK(success); +} + +bool HistogramSamples::AddFromPickle(PickleIterator* iter) { + int64 sum; + HistogramBase::Count redundant_count; + + if (!iter->ReadInt64(&sum) || !iter->ReadInt(&redundant_count)) + return false; + sum_ += sum; + redundant_count_ += redundant_count; + + SampleCountPickleIterator pickle_iter(iter); + return AddSubtractImpl(&pickle_iter, ADD); +} + +void HistogramSamples::Subtract(const HistogramSamples& other) { + sum_ -= other.sum(); + redundant_count_ -= other.redundant_count(); + bool success = AddSubtractImpl(other.Iterator().get(), SUBTRACT); + DCHECK(success); +} + +bool HistogramSamples::Serialize(Pickle* pickle) const { + if (!pickle->WriteInt64(sum_) || !pickle->WriteInt(redundant_count_)) + return false; + + HistogramBase::Sample min; + HistogramBase::Sample max; + HistogramBase::Count count; + for (scoped_ptr<SampleCountIterator> it = Iterator(); + !it->Done(); + it->Next()) { + it->Get(&min, &max, &count); + if (!pickle->WriteInt(min) || + !pickle->WriteInt(max) || + !pickle->WriteInt(count)) + return false; + } + return true; +} + +void HistogramSamples::IncreaseSum(int64 diff) { + sum_ += diff; +} + +void HistogramSamples::IncreaseRedundantCount(HistogramBase::Count diff) { + redundant_count_ += diff; +} + +SampleCountIterator::~SampleCountIterator() {} + +bool SampleCountIterator::GetBucketIndex(size_t* index) const { + DCHECK(!Done()); + return false; +} + +} // namespace base diff --git a/base/metrics/histogram_samples.h b/base/metrics/histogram_samples.h new file mode 100644 index 0000000..778b11d --- /dev/null +++ b/base/metrics/histogram_samples.h @@ -0,0 +1,89 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_METRICS_HISTOGRAM_SAMPLES_H_ +#define BASE_METRICS_HISTOGRAM_SAMPLES_H_ + +#include "base/basictypes.h" +#include "base/metrics/histogram_base.h" +#include "base/memory/scoped_ptr.h" + +class Pickle; +class PickleIterator; + +namespace base { + +class SampleCountIterator; + +// HistogramSamples is a container storing all samples of a histogram. +class BASE_EXPORT HistogramSamples { + public: + HistogramSamples(); + virtual ~HistogramSamples(); + + virtual void Accumulate(HistogramBase::Sample value, + HistogramBase::Count count) = 0; + virtual HistogramBase::Count GetCount(HistogramBase::Sample value) const = 0; + virtual HistogramBase::Count TotalCount() const = 0; + + virtual void Add(const HistogramSamples& other); + + // Add from serialized samples. + virtual bool AddFromPickle(PickleIterator* iter); + + virtual void Subtract(const HistogramSamples& other); + + virtual scoped_ptr<SampleCountIterator> Iterator() const = 0; + virtual bool Serialize(Pickle* pickle) const; + + // Accessor fuctions. + int64 sum() const { return sum_; } + HistogramBase::Count redundant_count() const { return redundant_count_; } + + protected: + // Based on |instruction| type, add or subtract sample counts data from the + // iterator. + enum Instruction { ADD, SUBTRACT }; + virtual bool AddSubtractImpl(SampleCountIterator* iter, + Instruction instruction) = 0; + + void IncreaseSum(int64 diff); + void IncreaseRedundantCount(HistogramBase::Count diff); + + private: + int64 sum_; + + // |redundant_count_| helps identify memory corruption. It redundantly stores + // the total number of samples accumulated in the histogram. We can compare + // this count to the sum of the counts (TotalCount() function), and detect + // problems. Note, depending on the implementation of different histogram + // types, there might be races during histogram accumulation and snapshotting + // that we choose to accept. In this case, the tallies might mismatch even + // when no memory corruption has happened. + HistogramBase::Count redundant_count_; +}; + +class BASE_EXPORT SampleCountIterator { + public: + virtual ~SampleCountIterator(); + + virtual bool Done() const = 0; + virtual void Next() = 0; + + // Get the sample and count at current position. + // |min| |max| and |count| can be NULL if the value is not of interest. + // Requires: !Done(); + virtual void Get(HistogramBase::Sample* min, + HistogramBase::Sample* max, + HistogramBase::Count* count) const = 0; + + // Get the index of current histogram bucket. + // For histograms that don't use predefined buckets, it returns false. + // Requires: !Done(); + virtual bool GetBucketIndex(size_t* index) const; +}; + +} // namespace base + +#endif // BASE_METRICS_HISTOGRAM_SAMPLES_H_ diff --git a/base/metrics/histogram_snapshot_manager.cc b/base/metrics/histogram_snapshot_manager.cc index b75932c..ad054b1 100644 --- a/base/metrics/histogram_snapshot_manager.cc +++ b/base/metrics/histogram_snapshot_manager.cc @@ -4,10 +4,14 @@ #include "base/metrics/histogram_snapshot_manager.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/histogram_flattener.h" +#include "base/metrics/histogram_samples.h" #include "base/metrics/statistics_recorder.h" +#include "base/stl_util.h" -using base::Histogram; -using base::StatisticsRecorder; +using std::map; +using std::string; namespace base { @@ -17,7 +21,9 @@ HistogramSnapshotManager::HistogramSnapshotManager( DCHECK(histogram_flattener_); } -HistogramSnapshotManager::~HistogramSnapshotManager() {} +HistogramSnapshotManager::~HistogramSnapshotManager() { + STLDeleteValues(&logged_samples_); +} void HistogramSnapshotManager::PrepareDeltas(Histogram::Flags flag_to_set, bool record_only_uma) { @@ -38,11 +44,10 @@ void HistogramSnapshotManager::PrepareDelta(const Histogram& histogram) { DCHECK(histogram_flattener_); // Get up-to-date snapshot of sample stats. - Histogram::SampleSet snapshot; - histogram.SnapshotSample(&snapshot); + scoped_ptr<HistogramSamples> snapshot(histogram.SnapshotSamples()); const std::string& histogram_name = histogram.histogram_name(); - int corruption = histogram.FindCorruption(snapshot); + int corruption = histogram.FindCorruption(*snapshot); // Crash if we detect that our histograms have been overwritten. This may be // a fair distance from the memory smasher, but we hope to correlate these @@ -59,54 +64,54 @@ void HistogramSnapshotManager::PrepareDelta(const Histogram& histogram) { // COUNT_LOW_ERROR and they never arise together, so we don't need to extract // bits from corruption. if (corruption) { - NOTREACHED(); + DLOG(ERROR) << "Histogram: " << histogram_name + << " has data corruption: " << corruption; histogram_flattener_->InconsistencyDetected( static_cast<Histogram::Inconsistencies>(corruption)); - // Don't record corrupt data to metrics survices. - if (NULL == inconsistencies_.get()) - inconsistencies_.reset(new ProblemMap); - int old_corruption = (*inconsistencies_)[histogram_name]; + // Don't record corrupt data to metrics services. + int old_corruption = inconsistencies_[histogram_name]; if (old_corruption == (corruption | old_corruption)) return; // We've already seen this corruption for this histogram. - (*inconsistencies_)[histogram_name] |= corruption; + inconsistencies_[histogram_name] |= corruption; histogram_flattener_->UniqueInconsistencyDetected( static_cast<Histogram::Inconsistencies>(corruption)); return; } - // Find the already recorded stats, or create an empty set. Remove from our - // snapshot anything that we've already recorded. - LoggedSampleMap::iterator it = logged_samples_.find(histogram_name); - Histogram::SampleSet* already_logged; - if (logged_samples_.end() == it) { - // Add new entry - already_logged = &logged_samples_[histogram.histogram_name()]; - // Complete initialization. - already_logged->Resize(histogram.bucket_count()); + HistogramSamples* to_log; + map<string, HistogramSamples*>::iterator it = + logged_samples_.find(histogram_name); + if (it == logged_samples_.end()) { + to_log = snapshot.release(); + + // This histogram has not been logged before, add a new entry. + logged_samples_[histogram_name] = to_log; } else { - already_logged = &(it->second); - int64 discrepancy(already_logged->TotalCount() - - already_logged->redundant_count()); - if (discrepancy) { - NOTREACHED(); // Already_logged has become corrupt. - int problem = static_cast<int>(discrepancy); - if (problem != discrepancy) - problem = INT_MAX; - histogram_flattener_->InconsistencyDetectedInLoggedCount(problem); - // With no valid baseline, we'll act like we've recorded everything in our - // snapshot. - already_logged->Subtract(*already_logged); - already_logged->Add(snapshot); - } - // Deduct any stats we've already logged from our snapshot. - snapshot.Subtract(*already_logged); + HistogramSamples* already_logged = it->second; + InspectLoggedSamplesInconsistency(*snapshot, already_logged); + snapshot->Subtract(*already_logged); + already_logged->Add(*snapshot); + to_log = snapshot.get(); } - // Snapshot now contains only a delta to what we've already_logged. - if (snapshot.redundant_count() > 0) { - histogram_flattener_->RecordDelta(histogram, snapshot); - // Add new data into our running total. - already_logged->Add(snapshot); + if (to_log->redundant_count() > 0) + histogram_flattener_->RecordDelta(histogram, *to_log); +} + +void HistogramSnapshotManager::InspectLoggedSamplesInconsistency( + const HistogramSamples& new_snapshot, + HistogramSamples* logged_samples) { + HistogramBase::Count discrepancy = + logged_samples->TotalCount() - logged_samples->redundant_count(); + if (!discrepancy) + return; + + histogram_flattener_->InconsistencyDetectedInLoggedCount(discrepancy); + if (discrepancy > Histogram::kCommonRaceBasedCountMismatch) { + // Fix logged_samples. + logged_samples->Subtract(*logged_samples); + logged_samples->Add(new_snapshot); } } + } // namespace base diff --git a/base/metrics/histogram_snapshot_manager.h b/base/metrics/histogram_snapshot_manager.h index a945ac7..0ec1ac3 100644 --- a/base/metrics/histogram_snapshot_manager.h +++ b/base/metrics/histogram_snapshot_manager.h @@ -9,12 +9,13 @@ #include <string> #include "base/basictypes.h" -#include "base/memory/scoped_ptr.h" #include "base/metrics/histogram.h" -#include "base/metrics/histogram_flattener.h" namespace base { +class HistogramSamples; +class HistogramFlattener; + // HistogramSnapshotManager handles the logistics of gathering up available // histograms for recording either to disk or for transmission (such as from // renderer to browser, or from browser to UMA upload). Since histograms can sit @@ -30,23 +31,23 @@ class BASE_EXPORT HistogramSnapshotManager { // Snapshot all histograms, and ask |histogram_flattener_| to record the // delta. The arguments allow selecting only a subset of histograms for // recording, or to set a flag in each recorded histogram. - void PrepareDeltas(base::Histogram::Flags flags_to_set, bool record_only_uma); + void PrepareDeltas(Histogram::Flags flags_to_set, bool record_only_uma); private: - // Maintain a map of histogram names to the sample stats we've recorded. - typedef std::map<std::string, base::Histogram::SampleSet> LoggedSampleMap; - // List of histograms names, and their encontered corruptions. - typedef std::map<std::string, int> ProblemMap; - // Snapshot this histogram, and record the delta. - void PrepareDelta(const base::Histogram& histogram); + void PrepareDelta(const Histogram& histogram); + + // Try to detect and fix count inconsistency of logged samples. + void InspectLoggedSamplesInconsistency( + const HistogramSamples& new_snapshot, + HistogramSamples* logged_samples); // For histograms, track what we've already recorded (as a sample for // each histogram) so that we can record only the delta with the next log. - LoggedSampleMap logged_samples_; + std::map<std::string, HistogramSamples*> logged_samples_; - // List of histograms found corrupt to be corrupt, and their problems. - scoped_ptr<ProblemMap> inconsistencies_; + // List of histograms found to be corrupt, and their problems. + std::map<std::string, int> inconsistencies_; // |histogram_flattener_| handles the logistics of recording the histogram // deltas. diff --git a/base/metrics/histogram_unittest.cc b/base/metrics/histogram_unittest.cc index 7f23ede..f016487 100644 --- a/base/metrics/histogram_unittest.cc +++ b/base/metrics/histogram_unittest.cc @@ -4,6 +4,7 @@ // Test of Histogram class +#include <climits> #include <algorithm> #include <vector> @@ -11,6 +12,7 @@ #include "base/memory/scoped_ptr.h" #include "base/metrics/bucket_ranges.h" #include "base/metrics/histogram.h" +#include "base/metrics/sample_vector.h" #include "base/metrics/statistics_recorder.h" #include "base/time.h" #include "testing/gtest/include/gtest/gtest.h" @@ -206,14 +208,13 @@ TEST(HistogramTest, BoundsTest) { histogram->Add(10000); // Verify they landed in the underflow, and overflow buckets. - Histogram::SampleSet sample; - histogram->SnapshotSample(&sample); - EXPECT_EQ(2, sample.counts(0)); - EXPECT_EQ(0, sample.counts(1)); + scoped_ptr<SampleVector> samples = histogram->SnapshotSamples(); + EXPECT_EQ(2, samples->GetCountAtIndex(0)); + EXPECT_EQ(0, samples->GetCountAtIndex(1)); size_t array_size = histogram->bucket_count(); EXPECT_EQ(kBucketCount, array_size); - EXPECT_EQ(0, sample.counts(array_size - 2)); - EXPECT_EQ(2, sample.counts(array_size - 1)); + EXPECT_EQ(0, samples->GetCountAtIndex(array_size - 2)); + EXPECT_EQ(2, samples->GetCountAtIndex(array_size - 1)); vector<int> custom_ranges; custom_ranges.push_back(10); @@ -227,15 +228,16 @@ TEST(HistogramTest, BoundsTest) { test_custom_histogram->Add(-50); test_custom_histogram->Add(100); test_custom_histogram->Add(1000); + test_custom_histogram->Add(INT_MAX); // Verify they landed in the underflow, and overflow buckets. - Histogram::SampleSet custom_sample; - test_custom_histogram->SnapshotSample(&custom_sample); - EXPECT_EQ(2, custom_sample.counts(0)); - EXPECT_EQ(0, custom_sample.counts(1)); - size_t custom_array_size = test_custom_histogram->bucket_count(); - EXPECT_EQ(0, custom_sample.counts(custom_array_size - 2)); - EXPECT_EQ(2, custom_sample.counts(custom_array_size - 1)); + scoped_ptr<SampleVector> custom_samples = + test_custom_histogram->SnapshotSamples(); + EXPECT_EQ(2, custom_samples->GetCountAtIndex(0)); + EXPECT_EQ(0, custom_samples->GetCountAtIndex(1)); + size_t bucket_count = test_custom_histogram->bucket_count(); + EXPECT_EQ(0, custom_samples->GetCountAtIndex(bucket_count - 2)); + EXPECT_EQ(3, custom_samples->GetCountAtIndex(bucket_count - 1)); } // Check to be sure samples land as expected is "correct" buckets. @@ -253,45 +255,45 @@ TEST(HistogramTest, BucketPlacementTest) { } // Check to see that the bucket counts reflect our additions. - Histogram::SampleSet sample; - histogram->SnapshotSample(&sample); + scoped_ptr<SampleVector> samples = histogram->SnapshotSamples(); for (int i = 0; i < 8; i++) - EXPECT_EQ(i + 1, sample.counts(i)); + EXPECT_EQ(i + 1, samples->GetCountAtIndex(i)); } TEST(HistogramTest, CorruptSampleCounts) { Histogram* histogram(Histogram::FactoryGet( "Histogram", 1, 64, 8, Histogram::kNoFlags)); // As per header file. - EXPECT_EQ(0, histogram->sample_.redundant_count()); - histogram->Add(20); // Add some samples. + // Add some samples. + histogram->Add(20); histogram->Add(40); - EXPECT_EQ(2, histogram->sample_.redundant_count()); - Histogram::SampleSet snapshot; - histogram->SnapshotSample(&snapshot); - EXPECT_EQ(Histogram::NO_INCONSISTENCIES, 0); - EXPECT_EQ(0, histogram->FindCorruption(snapshot)); // No default corruption. - EXPECT_EQ(2, snapshot.redundant_count()); + scoped_ptr<SampleVector> snapshot = histogram->SnapshotSamples(); + EXPECT_EQ(Histogram::NO_INCONSISTENCIES, + histogram->FindCorruption(*snapshot)); + EXPECT_EQ(2, snapshot->redundant_count()); + EXPECT_EQ(2, snapshot->TotalCount()); - snapshot.counts_[3] += 100; // Sample count won't match redundant count. - EXPECT_EQ(Histogram::COUNT_LOW_ERROR, histogram->FindCorruption(snapshot)); - snapshot.counts_[2] -= 200; - EXPECT_EQ(Histogram::COUNT_HIGH_ERROR, histogram->FindCorruption(snapshot)); + snapshot->counts_[3] += 100; // Sample count won't match redundant count. + EXPECT_EQ(Histogram::COUNT_LOW_ERROR, + histogram->FindCorruption(*snapshot)); + snapshot->counts_[2] -= 200; + EXPECT_EQ(Histogram::COUNT_HIGH_ERROR, + histogram->FindCorruption(*snapshot)); // But we can't spot a corruption if it is compensated for. - snapshot.counts_[1] += 100; - EXPECT_EQ(0, histogram->FindCorruption(snapshot)); + snapshot->counts_[1] += 100; + EXPECT_EQ(Histogram::NO_INCONSISTENCIES, + histogram->FindCorruption(*snapshot)); } TEST(HistogramTest, CorruptBucketBounds) { Histogram* histogram(Histogram::FactoryGet( "Histogram", 1, 64, 8, Histogram::kNoFlags)); // As per header file. - Histogram::SampleSet snapshot; - histogram->SnapshotSample(&snapshot); - EXPECT_EQ(Histogram::NO_INCONSISTENCIES, 0); - EXPECT_EQ(0, histogram->FindCorruption(snapshot)); // No default corruption. + scoped_ptr<SampleVector> snapshot = histogram->SnapshotSamples(); + EXPECT_EQ(Histogram::NO_INCONSISTENCIES, + histogram->FindCorruption(*snapshot)); BucketRanges* bucket_ranges = const_cast<BucketRanges*>(histogram->bucket_ranges()); @@ -299,20 +301,20 @@ TEST(HistogramTest, CorruptBucketBounds) { bucket_ranges->set_range(1, bucket_ranges->range(2)); bucket_ranges->set_range(2, tmp); EXPECT_EQ(Histogram::BUCKET_ORDER_ERROR | Histogram::RANGE_CHECKSUM_ERROR, - histogram->FindCorruption(snapshot)); + histogram->FindCorruption(*snapshot)); bucket_ranges->set_range(2, bucket_ranges->range(1)); bucket_ranges->set_range(1, tmp); - EXPECT_EQ(0, histogram->FindCorruption(snapshot)); + EXPECT_EQ(0, histogram->FindCorruption(*snapshot)); + // Show that two simple changes don't offset each other bucket_ranges->set_range(3, bucket_ranges->range(3) + 1); EXPECT_EQ(Histogram::RANGE_CHECKSUM_ERROR, - histogram->FindCorruption(snapshot)); + histogram->FindCorruption(*snapshot)); - // Show that two simple changes don't offset each other bucket_ranges->set_range(4, bucket_ranges->range(4) - 1); EXPECT_EQ(Histogram::RANGE_CHECKSUM_ERROR, - histogram->FindCorruption(snapshot)); + histogram->FindCorruption(*snapshot)); // Repair histogram so that destructor won't DCHECK(). bucket_ranges->set_range(3, bucket_ranges->range(3) - 1); diff --git a/base/metrics/sample_vector.cc b/base/metrics/sample_vector.cc new file mode 100644 index 0000000..327a1f2 --- /dev/null +++ b/base/metrics/sample_vector.cc @@ -0,0 +1,160 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/metrics/sample_vector.h" + +#include "base/logging.h" +#include "base/metrics/bucket_ranges.h" + +using std::vector; + +namespace base { + +typedef HistogramBase::Count Count; +typedef HistogramBase::Sample Sample; + +SampleVector::SampleVector(const BucketRanges* bucket_ranges) + : counts_(bucket_ranges->size() - 1), + bucket_ranges_(bucket_ranges) { + CHECK_GE(bucket_ranges_->size(), 2u); +} + +SampleVector::~SampleVector() {} + +void SampleVector::Accumulate(Sample value, Count count) { + size_t bucket_index = GetBucketIndex(value); + counts_[bucket_index] += count; + IncreaseSum(count * value); + IncreaseRedundantCount(count); +} + +Count SampleVector::GetCount(Sample value) const { + size_t bucket_index = GetBucketIndex(value); + return counts_[bucket_index]; +} + +Count SampleVector::TotalCount() const { + Count count = 0; + for (size_t i = 0; i < counts_.size(); i++) { + count += counts_[i]; + } + return count; +} + +Count SampleVector::GetCountAtIndex(size_t bucket_index) const { + DCHECK(bucket_index >= 0 && bucket_index < counts_.size()); + return counts_[bucket_index]; +} + +scoped_ptr<SampleCountIterator> SampleVector::Iterator() const { + return scoped_ptr<SampleCountIterator>( + new SampleVectorIterator(&counts_, bucket_ranges_)); +} + +bool SampleVector::AddSubtractImpl(SampleCountIterator* iter, + HistogramSamples::Instruction instruction) { + HistogramBase::Sample min; + HistogramBase::Sample max; + HistogramBase::Count count; + + // Go through the iterator and add the counts into correct bucket. + size_t index = 0; + while (index < counts_.size() && !iter->Done()) { + iter->Get(&min, &max, &count); + if (min == bucket_ranges_->range(index) && + max == bucket_ranges_->range(index + 1)) { + // Sample matches this bucket! + counts_[index] += + (instruction == HistogramSamples::ADD) ? count : -count; + iter->Next(); + } else if (min > bucket_ranges_->range(index)) { + // Sample is larger than current bucket range. Try next. + index++; + } else { + // Sample is smaller than current bucket range. We scan buckets from + // smallest to largest, so the sample value must be invalid. + return false; + } + } + + return iter->Done(); +} + +// Use simple binary search. This is very general, but there are better +// approaches if we knew that the buckets were linearly distributed. +size_t SampleVector::GetBucketIndex(Sample value) const { + size_t bucket_count = bucket_ranges_->size() - 1; + CHECK_GE(bucket_count, 1u); + CHECK_GE(value, bucket_ranges_->range(0)); + CHECK_LT(value, bucket_ranges_->range(bucket_count)); + + size_t under = 0; + size_t over = bucket_count; + size_t mid; + do { + DCHECK_GE(over, under); + mid = under + (over - under)/2; + if (mid == under) + break; + if (bucket_ranges_->range(mid) <= value) + under = mid; + else + over = mid; + } while (true); + + DCHECK_LE(bucket_ranges_->range(mid), value); + CHECK_GT(bucket_ranges_->range(mid + 1), value); + return mid; +} + +SampleVectorIterator::SampleVectorIterator(const vector<Count>* counts, + const BucketRanges* bucket_ranges) + : counts_(counts), + bucket_ranges_(bucket_ranges), + index_(0) { + CHECK_GT(bucket_ranges_->size(), counts_->size()); + SkipEmptyBuckets(); +} + +bool SampleVectorIterator::Done() const { + return index_ >= counts_->size(); +} + +void SampleVectorIterator::Next() { + DCHECK(!Done()); + index_++; + SkipEmptyBuckets(); +} + +void SampleVectorIterator::Get(HistogramBase::Sample* min, + HistogramBase::Sample* max, + HistogramBase::Count* count) const { + DCHECK(!Done()); + if (min != NULL) + *min = bucket_ranges_->range(index_); + if (max != NULL) + *max = bucket_ranges_->range(index_ + 1); + if (count != NULL) + *count = (*counts_)[index_]; +} + +bool SampleVectorIterator::GetBucketIndex(size_t* index) const { + DCHECK(!Done()); + if (index != NULL) + *index = index_; + return true; +} + +void SampleVectorIterator::SkipEmptyBuckets() { + if (Done()) + return; + + while (index_ < counts_->size()) { + if ((*counts_)[index_] != 0) + return; + index_++; + } +} + +} // namespace base diff --git a/base/metrics/sample_vector.h b/base/metrics/sample_vector.h new file mode 100644 index 0000000..8938d36 --- /dev/null +++ b/base/metrics/sample_vector.h @@ -0,0 +1,81 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// SampleVector implements HistogramSamples interface. It is used by all +// Histogram based classes to store samples. + +#ifndef BASE_METRICS_SAMPLE_VECTOR_H_ +#define BASE_METRICS_SAMPLE_VECTOR_H_ + +#include <vector> + +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/histogram_base.h" +#include "base/metrics/histogram_samples.h" + +namespace base { + +class BucketRanges; + +class BASE_EXPORT_PRIVATE SampleVector : public HistogramSamples { + public: + explicit SampleVector(const BucketRanges* bucket_ranges); + virtual ~SampleVector(); + + // HistogramSamples implementation: + virtual void Accumulate(HistogramBase::Sample value, + HistogramBase::Count count) OVERRIDE; + virtual HistogramBase::Count GetCount( + HistogramBase::Sample value) const OVERRIDE; + virtual HistogramBase::Count TotalCount() const OVERRIDE; + virtual scoped_ptr<SampleCountIterator> Iterator() const OVERRIDE; + + // Get count of a specific bucket. + HistogramBase::Count GetCountAtIndex(size_t bucket_index) const; + + protected: + virtual bool AddSubtractImpl( + SampleCountIterator* iter, + HistogramSamples::Instruction instruction) OVERRIDE; + + virtual size_t GetBucketIndex(HistogramBase::Sample value) const; + + private: + FRIEND_TEST_ALL_PREFIXES(HistogramTest, CorruptSampleCounts); + + std::vector<HistogramBase::Count> counts_; + + // Shares the same BucketRanges with Histogram object. + const BucketRanges* const bucket_ranges_; + + DISALLOW_COPY_AND_ASSIGN(SampleVector); +}; + +class BASE_EXPORT_PRIVATE SampleVectorIterator : public SampleCountIterator { + public: + SampleVectorIterator(const std::vector<HistogramBase::Count>* counts, + const BucketRanges* bucket_ranges); + + // SampleCountIterator implementation: + virtual bool Done() const OVERRIDE; + virtual void Next() OVERRIDE; + virtual void Get(HistogramBase::Sample* min, + HistogramBase::Sample* max, + HistogramBase::Count* count) const OVERRIDE; + virtual bool GetBucketIndex(size_t* index) const OVERRIDE; + + private: + void SkipEmptyBuckets(); + + const std::vector<HistogramBase::Count>* counts_; + const BucketRanges* bucket_ranges_; + + size_t index_; +}; + +} // namespace base + +#endif // BASE_METRICS_SAMPLE_VECTOR_H_ diff --git a/base/metrics/sample_vector_unittest.cc b/base/metrics/sample_vector_unittest.cc new file mode 100644 index 0000000..13e5e8b --- /dev/null +++ b/base/metrics/sample_vector_unittest.cc @@ -0,0 +1,265 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "base/metrics/bucket_ranges.h" +#include "base/metrics/histogram.h" +#include "base/metrics/sample_vector.h" +#include "testing/gtest/include/gtest/gtest.h" + +using std::vector; + +namespace base { +namespace { + +TEST(SampleVectorTest, AccumulateTest) { + // Custom buckets: [1, 5) [5, 10) + BucketRanges ranges(3); + ranges.set_range(0, 1); + ranges.set_range(1, 5); + ranges.set_range(2, 10); + SampleVector samples(&ranges); + + samples.Accumulate(1, 200); + samples.Accumulate(2, -300); + EXPECT_EQ(-100, samples.GetCountAtIndex(0)); + + samples.Accumulate(5, 200); + EXPECT_EQ(200, samples.GetCountAtIndex(1)); + + EXPECT_EQ(600, samples.sum()); + EXPECT_EQ(100, samples.redundant_count()); + EXPECT_EQ(samples.TotalCount(), samples.redundant_count()); + + samples.Accumulate(5, -100); + EXPECT_EQ(100, samples.GetCountAtIndex(1)); + + EXPECT_EQ(100, samples.sum()); + EXPECT_EQ(0, samples.redundant_count()); + EXPECT_EQ(samples.TotalCount(), samples.redundant_count()); +} + +TEST(SampleVectorTest, AddSubtractTest) { + // Custom buckets: [0, 1) [1, 2) [2, 3) [3, INT_MAX) + BucketRanges ranges(5); + ranges.set_range(0, 0); + ranges.set_range(1, 1); + ranges.set_range(2, 2); + ranges.set_range(3, 3); + ranges.set_range(4, INT_MAX); + + SampleVector samples1(&ranges); + samples1.Accumulate(0, 100); + samples1.Accumulate(2, 100); + samples1.Accumulate(4, 100); + EXPECT_EQ(600, samples1.sum()); + EXPECT_EQ(300, samples1.TotalCount()); + EXPECT_EQ(samples1.redundant_count(), samples1.TotalCount()); + + SampleVector samples2(&ranges); + samples2.Accumulate(1, 200); + samples2.Accumulate(2, 200); + samples2.Accumulate(4, 200); + EXPECT_EQ(1400, samples2.sum()); + EXPECT_EQ(600, samples2.TotalCount()); + EXPECT_EQ(samples2.redundant_count(), samples2.TotalCount()); + + samples1.Add(samples2); + EXPECT_EQ(100, samples1.GetCountAtIndex(0)); + EXPECT_EQ(200, samples1.GetCountAtIndex(1)); + EXPECT_EQ(300, samples1.GetCountAtIndex(2)); + EXPECT_EQ(300, samples1.GetCountAtIndex(3)); + EXPECT_EQ(2000, samples1.sum()); + EXPECT_EQ(900, samples1.TotalCount()); + EXPECT_EQ(samples1.redundant_count(), samples1.TotalCount()); + + samples1.Subtract(samples2); + EXPECT_EQ(100, samples1.GetCountAtIndex(0)); + EXPECT_EQ(0, samples1.GetCountAtIndex(1)); + EXPECT_EQ(100, samples1.GetCountAtIndex(2)); + EXPECT_EQ(100, samples1.GetCountAtIndex(3)); + EXPECT_EQ(600, samples1.sum()); + EXPECT_EQ(300, samples1.TotalCount()); + EXPECT_EQ(samples1.redundant_count(), samples1.TotalCount()); +} + +#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) && GTEST_HAS_DEATH_TEST +TEST(SampleVectorDeathTest, BucketIndexTest) { + // 8 buckets with exponential layout: + // [0, 1) [1, 2) [2, 4) [4, 8) [8, 16) [16, 32) [32, 64) [64, INT_MAX) + BucketRanges ranges(9); + Histogram::InitializeBucketRanges(1, 64, 8, &ranges); + SampleVector samples(&ranges); + + // Normal case + samples.Accumulate(0, 1); + samples.Accumulate(3, 2); + samples.Accumulate(64, 3); + EXPECT_EQ(1, samples.GetCount(0)); + EXPECT_EQ(2, samples.GetCount(2)); + EXPECT_EQ(3, samples.GetCount(65)); + + // Extreme case. + EXPECT_DEATH(samples.Accumulate(INT_MIN, 100), ""); + EXPECT_DEATH(samples.Accumulate(-1, 100), ""); + EXPECT_DEATH(samples.Accumulate(INT_MAX, 100), ""); + + // Custom buckets: [1, 5) [5, 10) + // Note, this is not a valid BucketRanges for Histogram because it does not + // have overflow buckets. + BucketRanges ranges2(3); + ranges2.set_range(0, 1); + ranges2.set_range(1, 5); + ranges2.set_range(2, 10); + SampleVector samples2(&ranges2); + + // Normal case. + samples2.Accumulate(1, 1); + samples2.Accumulate(4, 1); + samples2.Accumulate(5, 2); + samples2.Accumulate(9, 2); + EXPECT_EQ(2, samples2.GetCount(1)); + EXPECT_EQ(4, samples2.GetCount(5)); + + // Extreme case. + EXPECT_DEATH(samples2.Accumulate(0, 100), ""); + EXPECT_DEATH(samples2.Accumulate(10, 100), ""); +} + +TEST(SampleVectorDeathTest, AddSubtractBucketNotMatchTest) { + // Custom buckets 1: [1, 3) [3, 5) + BucketRanges ranges1(3); + ranges1.set_range(0, 1); + ranges1.set_range(1, 3); + ranges1.set_range(2, 5); + SampleVector samples1(&ranges1); + + // Custom buckets 2: [0, 1) [1, 3) [3, 6) [6, 7) + BucketRanges ranges2(5); + ranges2.set_range(0, 0); + ranges2.set_range(1, 1); + ranges2.set_range(2, 3); + ranges2.set_range(3, 6); + ranges2.set_range(4, 7); + SampleVector samples2(&ranges2); + + samples2.Accumulate(1, 100); + samples1.Add(samples2); + EXPECT_EQ(100, samples1.GetCountAtIndex(0)); + + // Extra bucket in the beginning. + samples2.Accumulate(0, 100); + EXPECT_DEATH(samples1.Add(samples2), ""); + EXPECT_DEATH(samples1.Subtract(samples2), ""); + + // Extra bucket in the end. + samples2.Accumulate(0, -100); + samples2.Accumulate(6, 100); + EXPECT_DEATH(samples1.Add(samples2), ""); + EXPECT_DEATH(samples1.Subtract(samples2), ""); + + // Bucket not match: [3, 5) VS [3, 6) + samples2.Accumulate(6, -100); + samples2.Accumulate(3, 100); + EXPECT_DEATH(samples1.Add(samples2), ""); + EXPECT_DEATH(samples1.Subtract(samples2), ""); +} + +// (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) && GTEST_HAS_DEATH_TEST +#endif + +TEST(SampleVectorIteratorTest, IterateTest) { + BucketRanges ranges(5); + ranges.set_range(0, 0); + ranges.set_range(1, 1); + ranges.set_range(2, 2); + ranges.set_range(3, 3); + ranges.set_range(4, 4); + + vector<HistogramBase::Count> counts(3); + counts[0] = 1; + counts[1] = 0; // Iterator will bypass this empty bucket. + counts[2] = 2; + + // BucketRanges can have larger size than counts. + SampleVectorIterator it(&counts, &ranges); + size_t index; + + HistogramBase::Sample min; + HistogramBase::Sample max; + HistogramBase::Count count; + it.Get(&min, &max, &count); + EXPECT_EQ(0, min); + EXPECT_EQ(1, max); + EXPECT_EQ(1, count); + EXPECT_TRUE(it.GetBucketIndex(&index)); + EXPECT_EQ(0u, index); + + it.Next(); + it.Get(&min, &max, &count); + EXPECT_EQ(2, min); + EXPECT_EQ(3, max); + EXPECT_EQ(2, count); + EXPECT_TRUE(it.GetBucketIndex(&index)); + EXPECT_EQ(2u, index); + + it.Next(); + EXPECT_TRUE(it.Done()); + + // Create iterator from SampleVector. + SampleVector samples(&ranges); + samples.Accumulate(0, 0); + samples.Accumulate(1, 1); + samples.Accumulate(2, 2); + samples.Accumulate(3, 3); + scoped_ptr<SampleCountIterator> it2 = samples.Iterator(); + + int i; + for (i = 1; !it2->Done(); i++, it2->Next()) { + it2->Get(&min, &max, &count); + EXPECT_EQ(i, min); + EXPECT_EQ(i + 1, max); + EXPECT_EQ(i, count); + + size_t index; + EXPECT_TRUE(it2->GetBucketIndex(&index)); + EXPECT_EQ(static_cast<size_t>(i), index); + } + EXPECT_EQ(4, i); +} + +#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) && GTEST_HAS_DEATH_TEST + +TEST(SampleVectorIteratorDeathTest, IterateDoneTest) { + BucketRanges ranges(5); + ranges.set_range(0, 0); + ranges.set_range(1, 1); + ranges.set_range(2, 2); + ranges.set_range(3, 3); + ranges.set_range(4, INT_MAX); + SampleVector samples(&ranges); + + scoped_ptr<SampleCountIterator> it = samples.Iterator(); + + EXPECT_TRUE(it->Done()); + + HistogramBase::Sample min; + HistogramBase::Sample max; + HistogramBase::Count count; + EXPECT_DEATH(it->Get(&min, &max, &count), ""); + + EXPECT_DEATH(it->Next(), ""); + + samples.Accumulate(2, 100); + it = samples.Iterator(); + EXPECT_FALSE(it->Done()); +} + +// (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) && GTEST_HAS_DEATH_TEST +#endif + +} // namespace +} // namespace base |