1 // Copyright 2014 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/test/metrics/histogram_tester.h"
6
7 #include <stddef.h>
8
9 #include "base/metrics/histogram.h"
10 #include "base/metrics/histogram_samples.h"
11 #include "base/metrics/metrics_hashes.h"
12 #include "base/metrics/sample_map.h"
13 #include "base/metrics/sparse_histogram.h"
14 #include "base/metrics/statistics_recorder.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/stringprintf.h"
17 #include "testing/gtest/include/gtest/gtest.h"
18
19 namespace base {
20
HistogramTester()21 HistogramTester::HistogramTester() {
22 // Record any histogram data that exists when the object is created so it can
23 // be subtracted later.
24 for (const auto* const histogram : StatisticsRecorder::GetHistograms()) {
25 histograms_snapshot_[histogram->histogram_name()] =
26 histogram->SnapshotSamples();
27 }
28 }
29
30 HistogramTester::~HistogramTester() = default;
31
ExpectUniqueSample(StringPiece name,HistogramBase::Sample sample,HistogramBase::Count expected_bucket_count,const Location & location) const32 void HistogramTester::ExpectUniqueSample(
33 StringPiece name,
34 HistogramBase::Sample sample,
35 HistogramBase::Count expected_bucket_count,
36 const Location& location) const {
37 HistogramBase* histogram = StatisticsRecorder::FindHistogram(name);
38 if (histogram) {
39 int actual_bucket_count;
40 int actual_total_count;
41 GetBucketCountForSamples(*histogram, sample, &actual_bucket_count,
42 &actual_total_count);
43
44 EXPECT_TRUE(expected_bucket_count == actual_bucket_count &&
45 expected_bucket_count == actual_total_count)
46 << "Histogram \"" << name << "\" did not meet its expectations.\n"
47 << "Bucket " << sample << " should contain " << expected_bucket_count
48 << " samples and contained " << actual_bucket_count << " samples.\n"
49 << "The total count of samples in the histogram should be "
50 << expected_bucket_count << " and was " << actual_total_count << ".\n"
51 << SnapshotToString(*histogram) << "\n"
52 << "(expected at " << location.ToString() << ")";
53 } else {
54 // No histogram means there were zero samples.
55 EXPECT_EQ(0, expected_bucket_count)
56 << "Zero samples found for Histogram \"" << name << "\".\n"
57 << "(expected at " << location.ToString() << ")";
58 return;
59 }
60 }
61
ExpectUniqueTimeSample(StringPiece name,TimeDelta sample,HistogramBase::Count expected_bucket_count,const Location & location) const62 void HistogramTester::ExpectUniqueTimeSample(
63 StringPiece name,
64 TimeDelta sample,
65 HistogramBase::Count expected_bucket_count,
66 const Location& location) const {
67 ExpectUniqueSample(name, sample.InMilliseconds(), expected_bucket_count,
68 location);
69 }
70
ExpectBucketCount(StringPiece name,HistogramBase::Sample sample,HistogramBase::Count expected_count,const Location & location) const71 void HistogramTester::ExpectBucketCount(StringPiece name,
72 HistogramBase::Sample sample,
73 HistogramBase::Count expected_count,
74 const Location& location) const {
75 HistogramBase* histogram = StatisticsRecorder::FindHistogram(name);
76 if (histogram) {
77 int actual_count;
78 GetBucketCountForSamples(*histogram, sample, &actual_count,
79 /*total_count=*/nullptr);
80
81 EXPECT_EQ(expected_count, actual_count)
82 << "Histogram \"" << name
83 << "\" does not have the right number of samples (" << expected_count
84 << ") in the expected bucket (" << sample << "). It has ("
85 << actual_count << ").\n"
86 << SnapshotToString(*histogram) << "\n"
87 << "(expected at " << location.ToString() << ")";
88 } else {
89 // No histogram means there were zero samples.
90 EXPECT_EQ(0, expected_count)
91 << "Histogram \"" << name << "\" does not exist. "
92 << "(expected at " << location.ToString() << ")";
93 }
94 }
95
ExpectTotalCount(StringPiece name,HistogramBase::Count expected_count,const Location & location) const96 void HistogramTester::ExpectTotalCount(StringPiece name,
97 HistogramBase::Count expected_count,
98 const Location& location) const {
99 HistogramBase* histogram = StatisticsRecorder::FindHistogram(name);
100 if (histogram) {
101 int actual_count = GetTotalCountForSamples(*histogram);
102
103 EXPECT_EQ(expected_count, actual_count)
104 << "Histogram \"" << name
105 << "\" does not have the right total number of samples ("
106 << expected_count << "). It has (" << actual_count << ").\n"
107 << SnapshotToString(*histogram) << "\n"
108 << "(expected at " << location.ToString() << ")";
109 } else {
110 // No histogram means there were zero samples.
111 EXPECT_EQ(0, expected_count)
112 << "Histogram \"" << name << "\" does not exist. "
113 << "(expected at " << location.ToString() << ")";
114 }
115 }
116
ExpectTimeBucketCount(StringPiece name,TimeDelta sample,HistogramBase::Count count,const Location & location) const117 void HistogramTester::ExpectTimeBucketCount(StringPiece name,
118 TimeDelta sample,
119 HistogramBase::Count count,
120 const Location& location) const {
121 ExpectBucketCount(name, sample.InMilliseconds(), count, location);
122 }
123
GetTotalSum(StringPiece name) const124 int64_t HistogramTester::GetTotalSum(StringPiece name) const {
125 HistogramBase* histogram = StatisticsRecorder::FindHistogram(name);
126 if (!histogram)
127 return 0;
128
129 int64_t original_sum = 0;
130 auto original_samples_it = histograms_snapshot_.find(name);
131 if (original_samples_it != histograms_snapshot_.end())
132 original_sum = original_samples_it->second->sum();
133
134 std::unique_ptr<HistogramSamples> samples = histogram->SnapshotSamples();
135 return samples->sum() - original_sum;
136 }
137
GetAllSamples(StringPiece name) const138 std::vector<Bucket> HistogramTester::GetAllSamples(StringPiece name) const {
139 std::vector<Bucket> samples;
140 std::unique_ptr<HistogramSamples> snapshot =
141 GetHistogramSamplesSinceCreation(name);
142 if (snapshot) {
143 for (auto it = snapshot->Iterator(); !it->Done(); it->Next()) {
144 HistogramBase::Sample sample;
145 int64_t max;
146 HistogramBase::Count count;
147 it->Get(&sample, &max, &count);
148 samples.emplace_back(sample, count);
149 }
150 }
151 return samples;
152 }
153
GetBucketCount(StringPiece name,HistogramBase::Sample sample) const154 HistogramBase::Count HistogramTester::GetBucketCount(
155 StringPiece name,
156 HistogramBase::Sample sample) const {
157 HistogramBase* histogram = StatisticsRecorder::FindHistogram(name);
158 HistogramBase::Count count = 0;
159 if (histogram) {
160 GetBucketCountForSamples(*histogram, sample, &count,
161 /*total_count=*/nullptr);
162 }
163 return count;
164 }
165
GetBucketCountForSamples(const HistogramBase & histogram,HistogramBase::Sample sample,HistogramBase::Count * count,HistogramBase::Count * total_count) const166 void HistogramTester::GetBucketCountForSamples(
167 const HistogramBase& histogram,
168 HistogramBase::Sample sample,
169 HistogramBase::Count* count,
170 HistogramBase::Count* total_count) const {
171 std::unique_ptr<HistogramSamples> samples = histogram.SnapshotSamples();
172 *count = samples->GetCount(sample);
173 if (total_count)
174 *total_count = samples->TotalCount();
175 auto histogram_data = histograms_snapshot_.find(histogram.histogram_name());
176 if (histogram_data != histograms_snapshot_.end()) {
177 *count -= histogram_data->second->GetCount(sample);
178 if (total_count)
179 *total_count -= histogram_data->second->TotalCount();
180 }
181 }
182
GetTotalCountsForPrefix(StringPiece prefix) const183 HistogramTester::CountsMap HistogramTester::GetTotalCountsForPrefix(
184 StringPiece prefix) const {
185 EXPECT_TRUE(prefix.find('.') != StringPiece::npos)
186 << "|prefix| ought to contain at least one period, to avoid matching too"
187 << " many histograms.";
188
189 CountsMap result;
190
191 // Find candidate matches by using the logic built into GetSnapshot().
192 for (const HistogramBase* histogram : StatisticsRecorder::GetHistograms()) {
193 if (!StartsWith(histogram->histogram_name(), prefix,
194 CompareCase::SENSITIVE)) {
195 continue;
196 }
197 std::unique_ptr<HistogramSamples> new_samples =
198 GetHistogramSamplesSinceCreation(histogram->histogram_name());
199 // Omit unchanged histograms from the result.
200 if (new_samples->TotalCount()) {
201 result[histogram->histogram_name()] = new_samples->TotalCount();
202 }
203 }
204 return result;
205 }
206
207 std::unique_ptr<HistogramSamples>
GetHistogramSamplesSinceCreation(StringPiece histogram_name) const208 HistogramTester::GetHistogramSamplesSinceCreation(
209 StringPiece histogram_name) const {
210 HistogramBase* histogram = StatisticsRecorder::FindHistogram(histogram_name);
211 // Whether the histogram exists or not may not depend on the current test
212 // calling this method, but rather on which tests ran before and possibly
213 // generated a histogram or not (see http://crbug.com/473689). To provide a
214 // response which is independent of the previously run tests, this method
215 // creates empty samples in the absence of the histogram, rather than
216 // returning null.
217 if (!histogram) {
218 return std::unique_ptr<HistogramSamples>(
219 new SampleMap(HashMetricName(histogram_name)));
220 }
221 std::unique_ptr<HistogramSamples> named_samples =
222 histogram->SnapshotSamples();
223 auto original_samples_it = histograms_snapshot_.find(histogram_name);
224 if (original_samples_it != histograms_snapshot_.end())
225 named_samples->Subtract(*original_samples_it->second.get());
226 return named_samples;
227 }
228
GetAllHistogramsRecorded() const229 std::string HistogramTester::GetAllHistogramsRecorded() const {
230 std::string output;
231
232 for (const auto* const histogram : StatisticsRecorder::GetHistograms()) {
233 std::unique_ptr<HistogramSamples> named_samples =
234 histogram->SnapshotSamples();
235
236 for (const auto& histogram_data : histograms_snapshot_) {
237 if (histogram_data.first == histogram->histogram_name())
238 named_samples->Subtract(*histogram_data.second);
239 }
240
241 if (named_samples->TotalCount()) {
242 auto current_count = histogram->SnapshotSamples()->TotalCount();
243 StringAppendF(&output, "Histogram: %s recorded %d new samples.\n",
244 histogram->histogram_name(), named_samples->TotalCount());
245 if (current_count != named_samples->TotalCount()) {
246 StringAppendF(&output,
247 "WARNING: There were samples recorded to this histogram "
248 "before tester instantiation.\n");
249 }
250 histogram->WriteAscii(&output);
251 StringAppendF(&output, "\n");
252 }
253 }
254
255 return output;
256 }
257
GetTotalCountForSamples(const base::HistogramBase & histogram) const258 int HistogramTester::GetTotalCountForSamples(
259 const base::HistogramBase& histogram) const {
260 std::unique_ptr<HistogramSamples> samples = histogram.SnapshotSamples();
261 int actual_count = samples->TotalCount();
262 auto histogram_data = histograms_snapshot_.find(histogram.histogram_name());
263 if (histogram_data != histograms_snapshot_.end())
264 actual_count -= histogram_data->second->TotalCount();
265 return actual_count;
266 }
267
SnapshotToString(const base::HistogramBase & histogram) const268 std::string HistogramTester::SnapshotToString(
269 const base::HistogramBase& histogram) const {
270 std::unique_ptr<HistogramSamples> snapshot =
271 GetHistogramSamplesSinceCreation(histogram.histogram_name());
272
273 base::Value::Dict graph_dict =
274 snapshot->ToGraphDict(histogram.histogram_name(), histogram.flags());
275 std::string tmp;
276 // The header message describes this histogram samples (name of the histogram
277 // and median of the samples). The body contains an ASCII art histogram of the
278 // samples.
279 tmp.append(*graph_dict.FindString("header"));
280 tmp.append("\n");
281 tmp.append(*graph_dict.FindString("body"));
282 return tmp;
283 }
284
PrintTo(const Bucket & bucket,std::ostream * os)285 void PrintTo(const Bucket& bucket, std::ostream* os) {
286 *os << "Bucket " << bucket.min << ": " << bucket.count;
287 }
288
289 } // namespace base
290