xref: /aosp_15_r20/external/cronet/base/test/metrics/histogram_tester.h (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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