summaryrefslogtreecommitdiffstats
path: root/base/metrics
diff options
context:
space:
mode:
authorkaiwang@chromium.org <kaiwang@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-09-22 03:42:12 +0000
committerkaiwang@chromium.org <kaiwang@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-09-22 03:42:12 +0000
commit2f7d9cdf62cb6729838a9c67e1a6efea7c739302 (patch)
tree01b27817539910ecffacfceb0f3b27b6d7deb092 /base/metrics
parent00b8ae882e96f41c41c27d3070897f02dd75d30b (diff)
downloadchromium_src-2f7d9cdf62cb6729838a9c67e1a6efea7c739302.zip
chromium_src-2f7d9cdf62cb6729838a9c67e1a6efea7c739302.tar.gz
chromium_src-2f7d9cdf62cb6729838a9c67e1a6efea7c739302.tar.bz2
SampleSet -> HistogramSamples which can be reused by SparseHistogram
BUG=139612 Review URL: https://chromiumcodereview.appspot.com/10829466 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@158166 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base/metrics')
-rw-r--r--base/metrics/bucket_ranges.h2
-rw-r--r--base/metrics/histogram.cc228
-rw-r--r--base/metrics/histogram.h98
-rw-r--r--base/metrics/histogram_flattener.h6
-rw-r--r--base/metrics/histogram_samples.cc126
-rw-r--r--base/metrics/histogram_samples.h89
-rw-r--r--base/metrics/histogram_snapshot_manager.cc89
-rw-r--r--base/metrics/histogram_snapshot_manager.h25
-rw-r--r--base/metrics/histogram_unittest.cc80
-rw-r--r--base/metrics/sample_vector.cc160
-rw-r--r--base/metrics/sample_vector.h81
-rw-r--r--base/metrics/sample_vector_unittest.cc265
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