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