// Copyright 2014 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/test/metrics/histogram_tester.h" #include #include #include "base/metrics/histogram.h" #include "base/metrics/histogram_samples.h" #include "base/metrics/metrics_hashes.h" #include "base/metrics/sample_map.h" #include "base/metrics/sparse_histogram.h" #include "base/metrics/statistics_recorder.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "testing/gtest/include/gtest/gtest.h" namespace base { HistogramTester::HistogramTester() { // Record any histogram data that exists when the object is created so it can // be subtracted later. for (const auto* const histogram : StatisticsRecorder::GetHistograms()) { histograms_snapshot_[histogram->histogram_name()] = histogram->SnapshotSamples(); } } HistogramTester::~HistogramTester() = default; void HistogramTester::ExpectUniqueSample( std::string_view name, HistogramBase::Sample sample, HistogramBase::Count expected_bucket_count, const Location& location) const { HistogramBase* histogram = StatisticsRecorder::FindHistogram(name); if (histogram) { int actual_bucket_count; int actual_total_count; GetBucketCountForSamples(*histogram, sample, &actual_bucket_count, &actual_total_count); EXPECT_TRUE(expected_bucket_count == actual_bucket_count && expected_bucket_count == actual_total_count) << "Histogram \"" << name << "\" did not meet its expectations.\n" << "Bucket " << sample << " should contain " << expected_bucket_count << " samples and contained " << actual_bucket_count << " samples.\n" << "The total count of samples in the histogram should be " << expected_bucket_count << " and was " << actual_total_count << ".\n" << SnapshotToString(*histogram) << "\n" << "(expected at " << location.ToString() << ")"; } else { // No histogram means there were zero samples. EXPECT_EQ(0, expected_bucket_count) << "Zero samples found for Histogram \"" << name << "\".\n" << "(expected at " << location.ToString() << ")"; return; } } void HistogramTester::ExpectUniqueTimeSample( std::string_view name, TimeDelta sample, HistogramBase::Count expected_bucket_count, const Location& location) const { ExpectUniqueSample(name, sample.InMilliseconds(), expected_bucket_count, location); } void HistogramTester::ExpectBucketCount(std::string_view name, HistogramBase::Sample sample, HistogramBase::Count expected_count, const Location& location) const { HistogramBase* histogram = StatisticsRecorder::FindHistogram(name); if (histogram) { int actual_count; GetBucketCountForSamples(*histogram, sample, &actual_count, /*total_count=*/nullptr); EXPECT_EQ(expected_count, actual_count) << "Histogram \"" << name << "\" does not have the right number of samples (" << expected_count << ") in the expected bucket (" << sample << "). It has (" << actual_count << ").\n" << SnapshotToString(*histogram) << "\n" << "(expected at " << location.ToString() << ")"; } else { // No histogram means there were zero samples. EXPECT_EQ(0, expected_count) << "Histogram \"" << name << "\" does not exist. " << "(expected at " << location.ToString() << ")"; } } void HistogramTester::ExpectTotalCount(std::string_view name, HistogramBase::Count expected_count, const Location& location) const { HistogramBase* histogram = StatisticsRecorder::FindHistogram(name); if (histogram) { int actual_count = GetTotalCountForSamples(*histogram); EXPECT_EQ(expected_count, actual_count) << "Histogram \"" << name << "\" does not have the right total number of samples (" << expected_count << "). It has (" << actual_count << ").\n" << SnapshotToString(*histogram) << "\n" << "(expected at " << location.ToString() << ")"; } else { // No histogram means there were zero samples. EXPECT_EQ(0, expected_count) << "Histogram \"" << name << "\" does not exist. " << "(expected at " << location.ToString() << ")"; } } void HistogramTester::ExpectTimeBucketCount(std::string_view name, TimeDelta sample, HistogramBase::Count count, const Location& location) const { ExpectBucketCount(name, sample.InMilliseconds(), count, location); } int64_t HistogramTester::GetTotalSum(std::string_view name) const { HistogramBase* histogram = StatisticsRecorder::FindHistogram(name); if (!histogram) return 0; int64_t original_sum = 0; auto original_samples_it = histograms_snapshot_.find(name); if (original_samples_it != histograms_snapshot_.end()) original_sum = original_samples_it->second->sum(); std::unique_ptr samples = histogram->SnapshotSamples(); return samples->sum() - original_sum; } std::vector HistogramTester::GetAllSamples( std::string_view name) const { std::vector samples; std::unique_ptr snapshot = GetHistogramSamplesSinceCreation(name); if (snapshot) { for (auto it = snapshot->Iterator(); !it->Done(); it->Next()) { HistogramBase::Sample sample; int64_t max; HistogramBase::Count count; it->Get(&sample, &max, &count); samples.emplace_back(sample, count); } } return samples; } HistogramBase::Count HistogramTester::GetBucketCount( std::string_view name, HistogramBase::Sample sample) const { HistogramBase* histogram = StatisticsRecorder::FindHistogram(name); HistogramBase::Count count = 0; if (histogram) { GetBucketCountForSamples(*histogram, sample, &count, /*total_count=*/nullptr); } return count; } void HistogramTester::GetBucketCountForSamples( const HistogramBase& histogram, HistogramBase::Sample sample, HistogramBase::Count* count, HistogramBase::Count* total_count) const { std::unique_ptr samples = histogram.SnapshotSamples(); *count = samples->GetCount(sample); if (total_count) *total_count = samples->TotalCount(); auto histogram_data = histograms_snapshot_.find(histogram.histogram_name()); if (histogram_data != histograms_snapshot_.end()) { *count -= histogram_data->second->GetCount(sample); if (total_count) *total_count -= histogram_data->second->TotalCount(); } } HistogramTester::CountsMap HistogramTester::GetTotalCountsForPrefix( std::string_view prefix) const { EXPECT_TRUE(prefix.find('.') != StringPiece::npos) << "|prefix| ought to contain at least one period, to avoid matching too" << " many histograms."; CountsMap result; // Find candidate matches by using the logic built into GetSnapshot(). for (const HistogramBase* histogram : StatisticsRecorder::GetHistograms()) { if (!StartsWith(histogram->histogram_name(), prefix, CompareCase::SENSITIVE)) { continue; } std::unique_ptr new_samples = GetHistogramSamplesSinceCreation(histogram->histogram_name()); // Omit unchanged histograms from the result. if (new_samples->TotalCount()) { result[histogram->histogram_name()] = new_samples->TotalCount(); } } return result; } std::unique_ptr HistogramTester::GetHistogramSamplesSinceCreation( std::string_view histogram_name) const { HistogramBase* histogram = StatisticsRecorder::FindHistogram(histogram_name); // Whether the histogram exists or not may not depend on the current test // calling this method, but rather on which tests ran before and possibly // generated a histogram or not (see http://crbug.com/473689). To provide a // response which is independent of the previously run tests, this method // creates empty samples in the absence of the histogram, rather than // returning null. if (!histogram) { return std::unique_ptr( new SampleMap(HashMetricName(histogram_name))); } std::unique_ptr named_samples = histogram->SnapshotSamples(); auto original_samples_it = histograms_snapshot_.find(histogram_name); if (original_samples_it != histograms_snapshot_.end()) named_samples->Subtract(*original_samples_it->second.get()); return named_samples; } std::string HistogramTester::GetAllHistogramsRecorded() const { std::string output; for (const auto* const histogram : StatisticsRecorder::GetHistograms()) { std::unique_ptr named_samples = histogram->SnapshotSamples(); for (const auto& histogram_data : histograms_snapshot_) { if (histogram_data.first == histogram->histogram_name()) named_samples->Subtract(*histogram_data.second); } if (named_samples->TotalCount()) { auto current_count = histogram->SnapshotSamples()->TotalCount(); StringAppendF(&output, "Histogram: %s recorded %d new samples.\n", histogram->histogram_name(), named_samples->TotalCount()); if (current_count != named_samples->TotalCount()) { StringAppendF(&output, "WARNING: There were samples recorded to this histogram " "before tester instantiation.\n"); } histogram->WriteAscii(&output); StringAppendF(&output, "\n"); } } return output; } int HistogramTester::GetTotalCountForSamples( const base::HistogramBase& histogram) const { std::unique_ptr samples = histogram.SnapshotSamples(); int actual_count = samples->TotalCount(); auto histogram_data = histograms_snapshot_.find(histogram.histogram_name()); if (histogram_data != histograms_snapshot_.end()) actual_count -= histogram_data->second->TotalCount(); return actual_count; } std::string HistogramTester::SnapshotToString( const base::HistogramBase& histogram) const { std::unique_ptr snapshot = GetHistogramSamplesSinceCreation(histogram.histogram_name()); base::Value::Dict graph_dict = snapshot->ToGraphDict(histogram.histogram_name(), histogram.flags()); std::string tmp; // The header message describes this histogram samples (name of the histogram // and median of the samples). The body contains an ASCII art histogram of the // samples. tmp.append(*graph_dict.FindString("header")); tmp.append("\n"); tmp.append(*graph_dict.FindString("body")); return tmp; } void PrintTo(const Bucket& bucket, std::ostream* os) { *os << "Bucket " << bucket.min << ": " << bucket.count; } } // namespace base