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 #ifndef BASE_TEST_METRICS_HISTOGRAM_TESTER_H_
6 #define BASE_TEST_METRICS_HISTOGRAM_TESTER_H_
7
8 #include <functional>
9 #include <map>
10 #include <memory>
11 #include <ostream>
12 #include <string>
13 #include <string_view>
14 #include <type_traits>
15 #include <utility>
16 #include <vector>
17
18 #include "base/location.h"
19 #include "base/metrics/histogram.h"
20 #include "base/metrics/histogram_base.h"
21 #include "base/time/time.h"
22 #include "testing/gmock/include/gmock/gmock.h"
23
24 namespace base {
25
26 struct Bucket;
27 class HistogramSamples;
28
29 // HistogramTester provides a simple interface for examining histograms, UMA
30 // or otherwise. Tests can use this interface to verify that histogram data is
31 // getting logged as intended.
32 //
33 // Note: When using this class from a browser test, one might have to call
34 // SubprocessMetricsProvider::MergeHistogramDeltasForTesting() to sync the
35 // histogram data between the renderer and browser processes. If it is in a
36 // content browser test, then content::FetchHistogramsFromChildProcesses()
37 // should be used to achieve that.
38 // To test histograms in Java tests, use HistogramWatcher.
39 class HistogramTester {
40 public:
41 using CountsMap = std::map<std::string, HistogramBase::Count, std::less<>>;
42
43 // Takes a snapshot of all current histograms counts.
44 HistogramTester();
45
46 HistogramTester(const HistogramTester&) = delete;
47 HistogramTester& operator=(const HistogramTester&) = delete;
48
49 ~HistogramTester();
50
51 // EXPECTs that the number of samples in bucket |sample| of histogram |name|
52 // grew by |expected_bucket_count| since the HistogramTester was created and
53 // that no other bucket of the histogram gained any extra samples.
54 // If a bucket had samples before the HistogramTester was created, these
55 // samples are completely ignored.
56 void ExpectUniqueSample(std::string_view name,
57 HistogramBase::Sample sample,
58 HistogramBase::Count expected_bucket_count,
59 const Location& location = FROM_HERE) const;
60 template <typename T>
61 void ExpectUniqueSample(std::string_view name,
62 T sample,
63 HistogramBase::Count expected_bucket_count,
64 const Location& location = FROM_HERE) const {
65 ExpectUniqueSample(name, static_cast<HistogramBase::Sample>(sample),
66 expected_bucket_count, location);
67 }
68 void ExpectUniqueTimeSample(std::string_view name,
69 TimeDelta sample,
70 HistogramBase::Count expected_bucket_count,
71 const Location& location = FROM_HERE) const;
72
73 // EXPECTs that the number of samples in bucket |sample| of histogram |name|
74 // grew by |expected_count| since the HistogramTester was created. Samples in
75 // other buckets are ignored.
76 void ExpectBucketCount(std::string_view name,
77 HistogramBase::Sample sample,
78 HistogramBase::Count expected_count,
79 const Location& location = FROM_HERE) const;
80 template <typename T>
81 void ExpectBucketCount(std::string_view name,
82 T sample,
83 HistogramBase::Count expected_count,
84 const Location& location = FROM_HERE) const {
85 ExpectBucketCount(name, static_cast<HistogramBase::Sample>(sample),
86 expected_count, location);
87 }
88 void ExpectTimeBucketCount(std::string_view name,
89 TimeDelta sample,
90 HistogramBase::Count expected_count,
91 const Location& location = FROM_HERE) const;
92
93 // EXPECTs that the total number of samples in histogram |name|
94 // grew by |expected_count| since the HistogramTester was created.
95 void ExpectTotalCount(std::string_view name,
96 HistogramBase::Count expected_count,
97 const Location& location = FROM_HERE) const;
98
99 // Returns the sum of all samples recorded since the HistogramTester was
100 // created.
101 int64_t GetTotalSum(std::string_view name) const;
102
103 // Returns a list of all of the buckets recorded since creation of this
104 // object, as vector<Bucket>, where the Bucket represents the min boundary of
105 // the bucket and the count of samples recorded to that bucket since creation.
106 //
107 // Note: The histogram defines the bucket boundaries. If you test a histogram
108 // with exponential bucket sizes, this function may not be particularly useful
109 // because you would need to guess the bucket boundaries.
110 //
111 // Example usage, using gMock:
112 // EXPECT_THAT(histogram_tester.GetAllSamples("HistogramName"),
113 // ElementsAre(Bucket(1, 5), Bucket(2, 10), Bucket(3, 5)));
114 //
115 // If you want make empty bucket explicit, use the BucketsAre() matcher
116 // defined below:
117 // EXPECT_THAT(histogram_tester.GetAllSamples("HistogramName"),
118 // BucketsAre(Bucket(1, 0), Bucket(2, 10), Bucket(3, 5)));
119 //
120 // If you want to test a superset relation, prefer BucketsInclude() over
121 // IsSupersetOf() because the former handles empty buckets as expected:
122 // EXPECT_THAT(histogram_tester.GetAllSamples("HistogramName"),
123 // BucketsInclude(Bucket(1, 0), Bucket(2, 10), Bucket(3, 5)));
124 // With IsSupersetOf(), this expectation would always fail because
125 // GetAllSamples() does not contain empty buckets.
126 //
127 // If you build the expected list programmatically, you can use the matchers
128 // ElementsAreArray(), BucketsAreArray(), BucketsIncludeArray().
129 //
130 // If you prefer not to depend on gMock at the expense of a slightly less
131 // helpful failure message, use EXPECT_EQ:
132 // EXPECT_EQ(expected_buckets,
133 // histogram_tester.GetAllSamples("HistogramName"));
134 std::vector<Bucket> GetAllSamples(std::string_view name) const;
135
136 // Returns the value of the |sample| bucket for ths histogram |name|.
137 HistogramBase::Count GetBucketCount(std::string_view name,
138 HistogramBase::Sample sample) const;
139 template <typename T>
GetBucketCount(std::string_view name,T sample)140 HistogramBase::Count GetBucketCount(std::string_view name, T sample) const {
141 return GetBucketCount(name, static_cast<HistogramBase::Sample>(sample));
142 }
143
144 // Finds histograms whose names start with |prefix|, and returns them along
145 // with the counts of any samples added since the creation of this object.
146 // Histograms that are unchanged are omitted from the result. The return value
147 // is a map whose keys are the histogram name, and whose values are the sample
148 // count.
149 //
150 // This is useful for cases where the code under test is choosing among a
151 // family of related histograms and incrementing one of them. Typically you
152 // should pass the result of this function directly to EXPECT_THAT.
153 //
154 // Example usage, using gmock (which produces better failure messages):
155 // #include "testing/gmock/include/gmock/gmock.h"
156 // ...
157 // base::HistogramTester::CountsMap expected_counts;
158 // expected_counts["MyMetric.A"] = 1;
159 // expected_counts["MyMetric.B"] = 1;
160 // EXPECT_THAT(histogram_tester.GetTotalCountsForPrefix("MyMetric."),
161 // testing::ContainerEq(expected_counts));
162 CountsMap GetTotalCountsForPrefix(std::string_view prefix) const;
163
164 // Returns the HistogramSamples recorded since the creation of the
165 // HistogramTester.
166 std::unique_ptr<HistogramSamples> GetHistogramSamplesSinceCreation(
167 std::string_view histogram_name) const;
168
169 // Dumps all histograms that have had new samples added to them into a string,
170 // for debugging purposes. Note: this will dump the entire contents of any
171 // modified histograms and not just the modified buckets.
172 std::string GetAllHistogramsRecorded() const;
173
174 private:
175 // Returns the total number of values recorded for |histogram| since the
176 // HistogramTester was created.
177 int GetTotalCountForSamples(const HistogramBase& histogram) const;
178
179 // Sets |*sample_count| to number of samples by which bucket |sample| bucket
180 // grew since the HistogramTester was created. If |total_count| is non-null,
181 // sets |*total_count| to the number of samples recorded for |histogram|
182 // since the HistogramTester was created.
183 void GetBucketCountForSamples(const HistogramBase& histogram,
184 HistogramBase::Sample sample,
185 HistogramBase::Count* sample_count,
186 HistogramBase::Count* total_count) const;
187
188 // Returns the deltas for |histogram| since the HistogramTester was created
189 // as an ASCII art histogram for debugging purposes.
190 std::string SnapshotToString(const HistogramBase& histogram) const;
191
192 // Snapshot of all histograms recorded before the HistogramTester was created.
193 // Used to determine the histogram changes made during this instance's
194 // lifecycle.
195 std::map<std::string, std::unique_ptr<HistogramSamples>, std::less<>>
196 histograms_snapshot_;
197 };
198
199 struct Bucket {
BucketBucket200 Bucket(HistogramBase::Sample min, HistogramBase::Count count)
201 : min(min), count(count) {}
202
203 // A variant of the above constructor that accepts an `EnumType` for the `min`
204 // value. Typically, this `EnumType` is the C++ enum (class) that is
205 // associated with the metric this bucket is referring to.
206 //
207 // The constructor forwards to the above non-templated constructor. Therefore,
208 // `EnumType` must be implicitly convertible to `HistogramBase::Sample`.
209 template <typename MetricEnum,
210 typename = std::enable_if_t<std::is_enum_v<MetricEnum>>>
BucketBucket211 Bucket(MetricEnum min, HistogramBase::Count count)
212 : Bucket(static_cast<std::underlying_type_t<MetricEnum>>(min), count) {}
213
214 friend bool operator==(const Bucket&, const Bucket&) = default;
215
216 HistogramBase::Sample min;
217 HistogramBase::Count count;
218 };
219
220 void PrintTo(const Bucket& value, std::ostream* os);
221
222 // The BucketsAre[Array]() and BucketsInclude[Array]() matchers are intended to
223 // match GetAllSamples().
224 //
225 // Unlike the standard matchers UnorderedElementsAreArray() and IsSupersetOf(),
226 // they explicitly support empty buckets (`Bucket::count == 0`). Empty buckets
227 // need special handling because GetAllSamples() doesn't contain empty ones.
228
229 // BucketsAre() and BucketsAreArray() match a container that contains exactly
230 // the non-empty `buckets`.
231 //
232 // For example,
233 // EXPECT_THAT(histogram_tester.GetAllSamples("HistogramName"),
234 // BucketsAre(Bucket(Enum::A, 0),
235 // Bucket(Enum::B, 1),
236 // Bucket(Enum::C, 2)));
237 // - matches the actual value `{Bucket(B, 1), Bucket(C, 2)}`;
238 // - does not match `{Bucket(A, n), Bucket(B, 1), Bucket(C, 2)}` for any `n`
239 // (including `n == 0`).
240 template <typename BucketArray>
BucketsAreArray(BucketArray buckets)241 auto BucketsAreArray(BucketArray buckets) {
242 auto non_empty_buckets = buckets;
243 std::erase_if(non_empty_buckets, [](Bucket b) { return b.count == 0; });
244 return ::testing::UnorderedElementsAreArray(non_empty_buckets);
245 }
246
247 template <typename... BucketTypes>
BucketsAre(BucketTypes...buckets)248 auto BucketsAre(BucketTypes... buckets) {
249 return BucketsAreArray(std::vector<Bucket>{buckets...});
250 }
251
252 // BucketsInclude() and BucketsIncludeArray() are empty-bucket-friendly
253 // replacements of IsSupersetOf[Array](): they match a container that contains
254 // all non-empty `buckets` and none of the empty `buckets`.
255 //
256 // For example,
257 // EXPECT_THAT(histogram_tester.GetAllSamples("HistogramName"),
258 // BucketsInclude(Bucket(Enum::A, 0),
259 // Bucket(Enum::B, 1),
260 // Bucket(Enum::C, 2)));
261 // - matches `{Bucket(B, 1), Bucket(C, 2), Bucket(D, 3)}`;
262 // - not match `{Bucket(A, n), Bucket(B, 1), Bucket(C, 2), Bucket(D, 3)}` for
263 // any `n` (including `n == 0`).
264 template <typename BucketArray>
BucketsIncludeArray(const BucketArray & buckets)265 auto BucketsIncludeArray(const BucketArray& buckets) {
266 // The `empty_buckets` have `count == 0`, so the `HistogramBase::Sample`
267 // suffices.
268 std::vector<HistogramBase::Sample> empty_buckets;
269 std::vector<Bucket> non_empty_buckets;
270 for (const Bucket& b : buckets) {
271 if (b.count == 0) {
272 empty_buckets.push_back(b.min);
273 } else {
274 non_empty_buckets.push_back(b);
275 }
276 }
277 using ::testing::AllOf;
278 using ::testing::AnyOfArray;
279 using ::testing::Each;
280 using ::testing::Field;
281 using ::testing::IsSupersetOf;
282 using ::testing::Not;
283 return AllOf(
284 IsSupersetOf(non_empty_buckets),
285 Each(Field("Bucket::min", &Bucket::min, Not(AnyOfArray(empty_buckets)))));
286 }
287
288 template <typename... BucketTypes>
BucketsInclude(BucketTypes...buckets)289 auto BucketsInclude(BucketTypes... buckets) {
290 return BucketsIncludeArray(std::vector<Bucket>{buckets...});
291 }
292
293 } // namespace base
294
295 #endif // BASE_TEST_METRICS_HISTOGRAM_TESTER_H_
296