File: histogram_tester.h

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (317 lines) | stat: -rw-r--r-- 13,939 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
// 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.

#ifndef BASE_TEST_METRICS_HISTOGRAM_TESTER_H_
#define BASE_TEST_METRICS_HISTOGRAM_TESTER_H_

#include <functional>
#include <map>
#include <memory>
#include <ostream>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <vector>

#include "base/location.h"
#include "base/metrics/histogram.h"
#include "base/metrics/histogram_base.h"
#include "base/time/time.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_map.h"

namespace base {

struct Bucket;
class HistogramSamples;

// HistogramTester provides a simple interface for examining histograms, UMA
// or otherwise. Tests can use this interface to verify that histogram data is
// getting logged as intended.
//
// Note: When using this class from a browser test, one might have to call
// SubprocessMetricsProvider::MergeHistogramDeltasForTesting() to sync the
// histogram data between the renderer and browser processes. If it is in a
// content browser test, then content::FetchHistogramsFromChildProcesses()
// should be used to achieve that.
// To test histograms in Java tests, use HistogramWatcher.
class HistogramTester {
 public:
  using CountsMap = std::map<std::string, HistogramBase::Count32, std::less<>>;

  // Takes a snapshot of all current histograms counts.
  HistogramTester();

  HistogramTester(const HistogramTester&) = delete;
  HistogramTester& operator=(const HistogramTester&) = delete;

  ~HistogramTester();

  // EXPECTs that the number of samples in bucket |sample| of histogram |name|
  // grew by |expected_bucket_count| since the HistogramTester was created and
  // that no other bucket of the histogram gained any extra samples.
  // If a bucket had samples before the HistogramTester was created, these
  // samples are completely ignored.
  void ExpectUniqueSample(std::string_view name,
                          HistogramBase::Sample32 sample,
                          HistogramBase::Count32 expected_bucket_count,
                          const Location& location = FROM_HERE) const;
  template <typename T>
  void ExpectUniqueSample(std::string_view name,
                          T sample,
                          HistogramBase::Count32 expected_bucket_count,
                          const Location& location = FROM_HERE) const {
    ExpectUniqueSample(name, static_cast<HistogramBase::Sample32>(sample),
                       expected_bucket_count, location);
  }
  void ExpectUniqueTimeSample(std::string_view name,
                              TimeDelta sample,
                              HistogramBase::Count32 expected_bucket_count,
                              const Location& location = FROM_HERE) const;

  // EXPECTs that the number of samples in bucket |sample| of histogram |name|
  // grew by |expected_count| since the HistogramTester was created. Samples in
  // other buckets are ignored.
  void ExpectBucketCount(std::string_view name,
                         HistogramBase::Sample32 sample,
                         HistogramBase::Count32 expected_count,
                         const Location& location = FROM_HERE) const;
  template <typename T>
  void ExpectBucketCount(std::string_view name,
                         T sample,
                         HistogramBase::Count32 expected_count,
                         const Location& location = FROM_HERE) const {
    ExpectBucketCount(name, static_cast<HistogramBase::Sample32>(sample),
                      expected_count, location);
  }
  void ExpectTimeBucketCount(std::string_view name,
                             TimeDelta sample,
                             HistogramBase::Count32 expected_count,
                             const Location& location = FROM_HERE) const;

  // EXPECTs that the total number of samples in histogram |name|
  // grew by |expected_count| since the HistogramTester was created.
  void ExpectTotalCount(std::string_view name,
                        HistogramBase::Count32 expected_count,
                        const Location& location = FROM_HERE) const;

  // Returns the sum of all samples recorded since the HistogramTester was
  // created.
  int64_t GetTotalSum(std::string_view name) const;

  // Returns a list of all of the buckets recorded since creation of this
  // object, as vector<Bucket>, where the Bucket represents the min boundary of
  // the bucket and the count of samples recorded to that bucket since creation.
  //
  // Note: The histogram defines the bucket boundaries. If you test a histogram
  // with exponential bucket sizes, this function may not be particularly useful
  // because you would need to guess the bucket boundaries.
  //
  // Example usage, using gMock:
  //   EXPECT_THAT(histogram_tester.GetAllSamples("HistogramName"),
  //               ElementsAre(Bucket(1, 5), Bucket(2, 10), Bucket(3, 5)));
  //
  // If you want make empty bucket explicit, use the BucketsAre() matcher
  // defined below:
  //   EXPECT_THAT(histogram_tester.GetAllSamples("HistogramName"),
  //               BucketsAre(Bucket(1, 0), Bucket(2, 10), Bucket(3, 5)));
  //
  // If you want to test a superset relation, prefer BucketsInclude() over
  // IsSupersetOf() because the former handles empty buckets as expected:
  //   EXPECT_THAT(histogram_tester.GetAllSamples("HistogramName"),
  //               BucketsInclude(Bucket(1, 0), Bucket(2, 10), Bucket(3, 5)));
  // With IsSupersetOf(), this expectation would always fail because
  // GetAllSamples() does not contain empty buckets.
  //
  // If you build the expected list programmatically, you can use the matchers
  // ElementsAreArray(), BucketsAreArray(), BucketsIncludeArray().
  //
  // If you prefer not to depend on gMock at the expense of a slightly less
  // helpful failure message, use EXPECT_EQ:
  //   EXPECT_EQ(expected_buckets,
  //             histogram_tester.GetAllSamples("HistogramName"));
  std::vector<Bucket> GetAllSamples(std::string_view name) const;

  // Similar to `GetAllSamples`, but returns all of the buckets for all
  // histograms whose names start with `prefix`, recorded since creation of this
  // object.
  //
  // This is useful to ensure ensure that only the expected histograms are
  // recorded and none others are. This is better than negative assertions like
  // `EXPECT_THAT(h.GetAllSamples("HistogramName.Unexpected"), IsEmpty())`
  // because if there was a typo in "HistogramName.Unexpected", the assertion
  // would always succeed.
  //
  // Example usage:
  //   EXPECT_THAT(
  //     histogram_tester.GetAllSamplesForPrefix("HistogramName"),
  //     UnorderedElementsAre(
  //         Pair("HistogramName.Foo",
  //              BucketsAre(Bucket(1, 5), Bucket(2, 10))),
  //         Pair("HistogramName.Bar",
  //              BucketsAre(Bucket(1, 0), Bucket(3, 5)))));
  absl::flat_hash_map<std::string, std::vector<Bucket>> GetAllSamplesForPrefix(
      std::string_view prefix) const;

  // Returns the value of the |sample| bucket for ths histogram |name|.
  HistogramBase::Count32 GetBucketCount(std::string_view name,
                                      HistogramBase::Sample32 sample) const;
  template <typename T>
  HistogramBase::Count32 GetBucketCount(std::string_view name, T sample) const {
    return GetBucketCount(name, static_cast<HistogramBase::Sample32>(sample));
  }

  // Finds histograms whose names start with |prefix|, and returns them along
  // with the counts of any samples added since the creation of this object.
  // Histograms that are unchanged are omitted from the result. The return value
  // is a map whose keys are the histogram name, and whose values are the sample
  // count.
  //
  // This is useful for cases where the code under test is choosing among a
  // family of related histograms and incrementing one of them. Typically you
  // should pass the result of this function directly to EXPECT_THAT.
  //
  // Example usage, using gmock (which produces better failure messages):
  //   #include "testing/gmock/include/gmock/gmock.h"
  // ...
  //   base::HistogramTester::CountsMap expected_counts;
  //   expected_counts["MyMetric.A"] = 1;
  //   expected_counts["MyMetric.B"] = 1;
  //   EXPECT_THAT(histogram_tester.GetTotalCountsForPrefix("MyMetric."),
  //               testing::ContainerEq(expected_counts));
  CountsMap GetTotalCountsForPrefix(std::string_view prefix) const;

  // Returns the HistogramSamples recorded since the creation of the
  // HistogramTester.
  std::unique_ptr<HistogramSamples> GetHistogramSamplesSinceCreation(
      std::string_view histogram_name) const;

  // Dumps all histograms that have had new samples added to them into a string,
  // for debugging purposes. Note: this will dump the entire contents of any
  // modified histograms and not just the modified buckets.
  std::string GetAllHistogramsRecorded() const;

 private:
  // Returns the total number of values recorded for |histogram| since the
  // HistogramTester was created.
  int GetTotalCountForSamples(const HistogramBase& histogram) const;

  // Sets |*sample_count| to number of samples by which bucket |sample| bucket
  // grew since the HistogramTester was created. If |total_count| is non-null,
  // sets |*total_count| to the number of samples recorded for |histogram|
  // since the HistogramTester was created.
  void GetBucketCountForSamples(const HistogramBase& histogram,
                                HistogramBase::Sample32 sample,
                                HistogramBase::Count32* sample_count,
                                HistogramBase::Count32* total_count) const;

  // Returns the deltas for |histogram| since the HistogramTester was created
  // as an ASCII art histogram for debugging purposes.
  std::string SnapshotToString(const HistogramBase& histogram) const;

  // Snapshot of all histograms recorded before the HistogramTester was created.
  // Used to determine the histogram changes made during this instance's
  // lifecycle.
  std::map<std::string, std::unique_ptr<HistogramSamples>, std::less<>>
      histograms_snapshot_;
};

struct Bucket {
  Bucket(HistogramBase::Sample32 min, HistogramBase::Count32 count)
      : min(min), count(count) {}

  // A variant of the above constructor that accepts an `EnumType` for the `min`
  // value. Typically, this `EnumType` is the C++ enum (class) that is
  // associated with the metric this bucket is referring to.
  //
  // The constructor forwards to the above non-templated constructor. Therefore,
  // `EnumType` must be implicitly convertible to `HistogramBase::Sample32`.
  template <typename MetricEnum>
    requires(std::is_enum_v<MetricEnum>)
  Bucket(MetricEnum min, HistogramBase::Count32 count)
      : Bucket(static_cast<std::underlying_type_t<MetricEnum>>(min), count) {}

  friend bool operator==(const Bucket&, const Bucket&) = default;

  HistogramBase::Sample32 min;
  HistogramBase::Count32 count;
};

void PrintTo(const Bucket& value, std::ostream* os);

// The BucketsAre[Array]() and BucketsInclude[Array]() matchers are intended to
// match GetAllSamples().
//
// Unlike the standard matchers UnorderedElementsAreArray() and IsSupersetOf(),
// they explicitly support empty buckets (`Bucket::Count32 == 0`). Empty buckets
// need special handling because GetAllSamples() doesn't contain empty ones.

// BucketsAre() and BucketsAreArray() match a container that contains exactly
// the non-empty `buckets`.
//
// For example,
//   EXPECT_THAT(histogram_tester.GetAllSamples("HistogramName"),
//               BucketsAre(Bucket(Enum::A, 0),
//                          Bucket(Enum::B, 1),
//                          Bucket(Enum::C, 2)));
// - matches the actual value `{Bucket(B, 1), Bucket(C, 2)}`;
// - does not match `{Bucket(A, n), Bucket(B, 1), Bucket(C, 2)}` for any `n`
//   (including `n == 0`).
template <typename BucketArray>
auto BucketsAreArray(BucketArray buckets) {
  auto non_empty_buckets = buckets;
  std::erase_if(non_empty_buckets, [](Bucket b) { return b.count == 0; });
  return ::testing::UnorderedElementsAreArray(non_empty_buckets);
}

template <typename... BucketTypes>
auto BucketsAre(BucketTypes... buckets) {
  return BucketsAreArray(std::vector<Bucket>{buckets...});
}

// BucketsInclude() and BucketsIncludeArray() are empty-bucket-friendly
// replacements of IsSupersetOf[Array](): they match a container that contains
// all non-empty `buckets` and none of the empty `buckets`.
//
// For example,
//   EXPECT_THAT(histogram_tester.GetAllSamples("HistogramName"),
//               BucketsInclude(Bucket(Enum::A, 0),
//                              Bucket(Enum::B, 1),
//                              Bucket(Enum::C, 2)));
// - matches `{Bucket(B, 1), Bucket(C, 2), Bucket(D, 3)}`;
// - not match `{Bucket(A, n), Bucket(B, 1), Bucket(C, 2), Bucket(D, 3)}` for
//   any `n` (including `n == 0`).
template <typename BucketArray>
auto BucketsIncludeArray(const BucketArray& buckets) {
  // The `empty_buckets` have `count == 0`, so the `HistogramBase::Sample32`
  // suffices.
  std::vector<HistogramBase::Sample32> empty_buckets;
  std::vector<Bucket> non_empty_buckets;
  for (const Bucket& b : buckets) {
    if (b.count == 0) {
      empty_buckets.push_back(b.min);
    } else {
      non_empty_buckets.push_back(b);
    }
  }
  using ::testing::AllOf;
  using ::testing::AnyOfArray;
  using ::testing::Each;
  using ::testing::Field;
  using ::testing::IsSupersetOf;
  using ::testing::Not;
  return AllOf(
      IsSupersetOf(non_empty_buckets),
      Each(Field("Bucket::min", &Bucket::min, Not(AnyOfArray(empty_buckets)))));
}

template <typename... BucketTypes>
auto BucketsInclude(BucketTypes... buckets) {
  return BucketsIncludeArray(std::vector<Bucket>{buckets...});
}

}  // namespace base

#endif  // BASE_TEST_METRICS_HISTOGRAM_TESTER_H_