xref: /aosp_15_r20/external/cronet/base/metrics/sparse_histogram_unittest.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2012 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/metrics/sparse_histogram.h"
6 
7 #include <memory>
8 #include <string>
9 #include <string_view>
10 #include <vector>
11 
12 #include "base/logging.h"
13 #include "base/memory/raw_ptr.h"
14 #include "base/metrics/histogram_base.h"
15 #include "base/metrics/histogram_functions.h"
16 #include "base/metrics/histogram_samples.h"
17 #include "base/metrics/metrics_hashes.h"
18 #include "base/metrics/persistent_histogram_allocator.h"
19 #include "base/metrics/persistent_memory_allocator.h"
20 #include "base/metrics/sample_map.h"
21 #include "base/metrics/statistics_recorder.h"
22 #include "base/pickle.h"
23 #include "base/strings/stringprintf.h"
24 #include "base/values.h"
25 #include "testing/gmock/include/gmock/gmock.h"
26 
27 namespace base {
28 
29 // Test parameter indicates if a persistent memory allocator should be used
30 // for histogram allocation. False will allocate histograms from the process
31 // heap.
32 class SparseHistogramTest : public testing::TestWithParam<bool> {
33  public:
SparseHistogramTest()34   SparseHistogramTest() : use_persistent_histogram_allocator_(GetParam()) {}
35   SparseHistogramTest(const SparseHistogramTest&) = delete;
36   SparseHistogramTest& operator=(const SparseHistogramTest&) = delete;
37 
38  protected:
39   const int32_t kAllocatorMemorySize = 8 << 20;  // 8 MiB
40 
41   using CountAndBucketData = base::SparseHistogram::CountAndBucketData;
42 
SetUp()43   void SetUp() override {
44     if (use_persistent_histogram_allocator_)
45       CreatePersistentMemoryAllocator();
46 
47     // Each test will have a clean state (no Histogram / BucketRanges
48     // registered).
49     InitializeStatisticsRecorder();
50   }
51 
TearDown()52   void TearDown() override {
53     if (allocator_) {
54       ASSERT_FALSE(allocator_->IsFull());
55       ASSERT_FALSE(allocator_->IsCorrupt());
56     }
57     UninitializeStatisticsRecorder();
58     DestroyPersistentMemoryAllocator();
59   }
60 
InitializeStatisticsRecorder()61   void InitializeStatisticsRecorder() {
62     DCHECK(!statistics_recorder_);
63     statistics_recorder_ = StatisticsRecorder::CreateTemporaryForTesting();
64   }
65 
UninitializeStatisticsRecorder()66   void UninitializeStatisticsRecorder() { statistics_recorder_.reset(); }
67 
CreatePersistentMemoryAllocator()68   void CreatePersistentMemoryAllocator() {
69     GlobalHistogramAllocator::CreateWithLocalMemory(
70         kAllocatorMemorySize, 0, "SparseHistogramAllocatorTest");
71     allocator_ = GlobalHistogramAllocator::Get()->memory_allocator();
72   }
73 
DestroyPersistentMemoryAllocator()74   void DestroyPersistentMemoryAllocator() {
75     allocator_ = nullptr;
76     GlobalHistogramAllocator::ReleaseForTesting();
77   }
78 
NewSparseHistogram(const char * name)79   std::unique_ptr<SparseHistogram> NewSparseHistogram(const char* name) {
80     // std::make_unique can't access protected ctor so do it manually. This
81     // test class is a friend so can access it.
82     return std::unique_ptr<SparseHistogram>(new SparseHistogram(name));
83   }
84 
GetCountAndBucketData(SparseHistogram * histogram)85   CountAndBucketData GetCountAndBucketData(SparseHistogram* histogram) {
86     // A simple wrapper around |GetCountAndBucketData| to make it visible for
87     // testing.
88     return histogram->GetCountAndBucketData();
89   }
90 
91   const bool use_persistent_histogram_allocator_;
92 
93   std::unique_ptr<StatisticsRecorder> statistics_recorder_;
94   raw_ptr<PersistentMemoryAllocator> allocator_ = nullptr;
95 };
96 
97 // Run all HistogramTest cases with both heap and persistent memory.
98 INSTANTIATE_TEST_SUITE_P(HeapAndPersistent,
99                          SparseHistogramTest,
100                          testing::Bool());
101 
TEST_P(SparseHistogramTest,BasicTest)102 TEST_P(SparseHistogramTest, BasicTest) {
103   std::unique_ptr<SparseHistogram> histogram(NewSparseHistogram("Sparse"));
104   std::unique_ptr<HistogramSamples> snapshot(histogram->SnapshotSamples());
105   EXPECT_EQ(0, snapshot->TotalCount());
106   EXPECT_EQ(0, snapshot->sum());
107 
108   histogram->Add(100);
109   std::unique_ptr<HistogramSamples> snapshot1(histogram->SnapshotSamples());
110   EXPECT_EQ(1, snapshot1->TotalCount());
111   EXPECT_EQ(1, snapshot1->GetCount(100));
112 
113   histogram->Add(100);
114   histogram->Add(101);
115   std::unique_ptr<HistogramSamples> snapshot2(histogram->SnapshotSamples());
116   EXPECT_EQ(3, snapshot2->TotalCount());
117   EXPECT_EQ(2, snapshot2->GetCount(100));
118   EXPECT_EQ(1, snapshot2->GetCount(101));
119 }
120 
TEST_P(SparseHistogramTest,BasicTestAddCount)121 TEST_P(SparseHistogramTest, BasicTestAddCount) {
122   std::unique_ptr<SparseHistogram> histogram(NewSparseHistogram("Sparse"));
123   std::unique_ptr<HistogramSamples> snapshot(histogram->SnapshotSamples());
124   EXPECT_EQ(0, snapshot->TotalCount());
125   EXPECT_EQ(0, snapshot->sum());
126 
127   histogram->AddCount(100, 15);
128   std::unique_ptr<HistogramSamples> snapshot1(histogram->SnapshotSamples());
129   EXPECT_EQ(15, snapshot1->TotalCount());
130   EXPECT_EQ(15, snapshot1->GetCount(100));
131 
132   histogram->AddCount(100, 15);
133   histogram->AddCount(101, 25);
134   std::unique_ptr<HistogramSamples> snapshot2(histogram->SnapshotSamples());
135   EXPECT_EQ(55, snapshot2->TotalCount());
136   EXPECT_EQ(30, snapshot2->GetCount(100));
137   EXPECT_EQ(25, snapshot2->GetCount(101));
138 }
139 
140 // Check that delta calculations work correctly with SnapshotUnloggedSamples()
141 // and MarkSamplesAsLogged().
TEST_P(SparseHistogramTest,UnloggedSamplesTest)142 TEST_P(SparseHistogramTest, UnloggedSamplesTest) {
143   std::unique_ptr<SparseHistogram> histogram(NewSparseHistogram("Sparse"));
144   histogram->AddCount(1, 1);
145   histogram->AddCount(2, 2);
146 
147   std::unique_ptr<HistogramSamples> samples =
148       histogram->SnapshotUnloggedSamples();
149   EXPECT_EQ(3, samples->TotalCount());
150   EXPECT_EQ(1, samples->GetCount(1));
151   EXPECT_EQ(2, samples->GetCount(2));
152   EXPECT_EQ(samples->TotalCount(), samples->redundant_count());
153   EXPECT_EQ(5, samples->sum());
154 
155   // Snapshot unlogged samples again, which would be the same as above.
156   samples = histogram->SnapshotUnloggedSamples();
157   EXPECT_EQ(3, samples->TotalCount());
158   EXPECT_EQ(1, samples->GetCount(1));
159   EXPECT_EQ(2, samples->GetCount(2));
160   EXPECT_EQ(samples->TotalCount(), samples->redundant_count());
161   EXPECT_EQ(5, samples->sum());
162 
163   // Verify that marking the samples as logged works correctly, and that
164   // SnapshotDelta() will not pick up the samples.
165   histogram->MarkSamplesAsLogged(*samples);
166   samples = histogram->SnapshotUnloggedSamples();
167   EXPECT_EQ(0, samples->TotalCount());
168   EXPECT_EQ(samples->TotalCount(), samples->redundant_count());
169   EXPECT_EQ(0, samples->sum());
170   samples = histogram->SnapshotDelta();
171   EXPECT_EQ(0, samples->TotalCount());
172   EXPECT_EQ(samples->TotalCount(), samples->redundant_count());
173   EXPECT_EQ(0, samples->sum());
174 
175   // Similarly, verify that SnapshotDelta() marks the samples as logged.
176   histogram->AddCount(1, 1);
177   histogram->AddCount(2, 2);
178   samples = histogram->SnapshotDelta();
179   EXPECT_EQ(3, samples->TotalCount());
180   EXPECT_EQ(1, samples->GetCount(1));
181   EXPECT_EQ(2, samples->GetCount(2));
182   EXPECT_EQ(samples->TotalCount(), samples->redundant_count());
183   EXPECT_EQ(5, samples->sum());
184   samples = histogram->SnapshotUnloggedSamples();
185   EXPECT_EQ(0, samples->TotalCount());
186   EXPECT_EQ(samples->TotalCount(), samples->redundant_count());
187   EXPECT_EQ(0, samples->sum());
188 
189   // Verify that the logged samples contain everything emitted.
190   samples = histogram->SnapshotSamples();
191   EXPECT_EQ(6, samples->TotalCount());
192   EXPECT_EQ(samples->TotalCount(), samples->redundant_count());
193   EXPECT_EQ(2, samples->GetCount(1));
194   EXPECT_EQ(4, samples->GetCount(2));
195   EXPECT_EQ(10, samples->sum());
196 }
197 
198 // Check that IsDefinitelyEmpty() works with the results of SnapshotDelta().
TEST_P(SparseHistogramTest,IsDefinitelyEmpty_SnapshotDelta)199 TEST_P(SparseHistogramTest, IsDefinitelyEmpty_SnapshotDelta) {
200   std::unique_ptr<SparseHistogram> histogram(NewSparseHistogram("Sparse"));
201 
202   // No samples initially.
203   EXPECT_TRUE(histogram->SnapshotDelta()->IsDefinitelyEmpty());
204 
205   histogram->Add(1);
206   EXPECT_FALSE(histogram->SnapshotDelta()->IsDefinitelyEmpty());
207   EXPECT_TRUE(histogram->SnapshotDelta()->IsDefinitelyEmpty());
208   histogram->Add(10);
209   histogram->Add(10);
210   EXPECT_FALSE(histogram->SnapshotDelta()->IsDefinitelyEmpty());
211   EXPECT_TRUE(histogram->SnapshotDelta()->IsDefinitelyEmpty());
212   histogram->Add(1);
213   histogram->Add(50);
214   EXPECT_FALSE(histogram->SnapshotDelta()->IsDefinitelyEmpty());
215   EXPECT_TRUE(histogram->SnapshotDelta()->IsDefinitelyEmpty());
216 }
217 
TEST_P(SparseHistogramTest,AddCount_LargeValuesDontOverflow)218 TEST_P(SparseHistogramTest, AddCount_LargeValuesDontOverflow) {
219   std::unique_ptr<SparseHistogram> histogram(NewSparseHistogram("Sparse"));
220   std::unique_ptr<HistogramSamples> snapshot(histogram->SnapshotSamples());
221   EXPECT_EQ(0, snapshot->TotalCount());
222   EXPECT_EQ(0, snapshot->sum());
223 
224   histogram->AddCount(1000000000, 15);
225   std::unique_ptr<HistogramSamples> snapshot1(histogram->SnapshotSamples());
226   EXPECT_EQ(15, snapshot1->TotalCount());
227   EXPECT_EQ(15, snapshot1->GetCount(1000000000));
228 
229   histogram->AddCount(1000000000, 15);
230   histogram->AddCount(1010000000, 25);
231   std::unique_ptr<HistogramSamples> snapshot2(histogram->SnapshotSamples());
232   EXPECT_EQ(55, snapshot2->TotalCount());
233   EXPECT_EQ(30, snapshot2->GetCount(1000000000));
234   EXPECT_EQ(25, snapshot2->GetCount(1010000000));
235   EXPECT_EQ(55250000000LL, snapshot2->sum());
236 }
237 
238 // Make sure that counts returned by Histogram::SnapshotDelta do not overflow
239 // even when a total count (returned by Histogram::SnapshotSample) does.
TEST_P(SparseHistogramTest,AddCount_LargeCountsDontOverflow)240 TEST_P(SparseHistogramTest, AddCount_LargeCountsDontOverflow) {
241   std::unique_ptr<SparseHistogram> histogram(NewSparseHistogram("Sparse"));
242   std::unique_ptr<HistogramSamples> snapshot(histogram->SnapshotSamples());
243   EXPECT_EQ(0, snapshot->TotalCount());
244   EXPECT_EQ(0, snapshot->sum());
245 
246   const int count = (1 << 30) - 1;
247 
248   // Repeat N times to make sure that there is no internal value overflow.
249   for (int i = 0; i < 10; ++i) {
250     histogram->AddCount(42, count);
251     std::unique_ptr<HistogramSamples> samples = histogram->SnapshotDelta();
252     EXPECT_EQ(count, samples->TotalCount());
253     EXPECT_EQ(count, samples->GetCount(42));
254   }
255 }
256 
TEST_P(SparseHistogramTest,MacroBasicTest)257 TEST_P(SparseHistogramTest, MacroBasicTest) {
258   UmaHistogramSparse("Sparse", 100);
259   UmaHistogramSparse("Sparse", 200);
260   UmaHistogramSparse("Sparse", 100);
261 
262   const StatisticsRecorder::Histograms histograms =
263       StatisticsRecorder::GetHistograms();
264 
265   ASSERT_THAT(histograms, testing::SizeIs(1));
266   const HistogramBase* const sparse_histogram = histograms[0];
267 
268   EXPECT_EQ(SPARSE_HISTOGRAM, sparse_histogram->GetHistogramType());
269   EXPECT_STREQ("Sparse", sparse_histogram->histogram_name());
270   EXPECT_EQ(
271       HistogramBase::kUmaTargetedHistogramFlag |
272           (use_persistent_histogram_allocator_ ? HistogramBase::kIsPersistent
273                                                : 0),
274       sparse_histogram->flags());
275 
276   std::unique_ptr<HistogramSamples> samples =
277       sparse_histogram->SnapshotSamples();
278   EXPECT_EQ(3, samples->TotalCount());
279   EXPECT_EQ(2, samples->GetCount(100));
280   EXPECT_EQ(1, samples->GetCount(200));
281 }
282 
TEST_P(SparseHistogramTest,MacroInLoopTest)283 TEST_P(SparseHistogramTest, MacroInLoopTest) {
284   // Unlike the macros in histogram.h, SparseHistogram macros can have a
285   // variable as histogram name.
286   for (int i = 0; i < 2; i++) {
287     UmaHistogramSparse(StringPrintf("Sparse%d", i), 100);
288   }
289 
290   const StatisticsRecorder::Histograms histograms =
291       StatisticsRecorder::Sort(StatisticsRecorder::GetHistograms());
292   ASSERT_THAT(histograms, testing::SizeIs(2));
293   EXPECT_STREQ(histograms[0]->histogram_name(), "Sparse0");
294   EXPECT_STREQ(histograms[1]->histogram_name(), "Sparse1");
295 }
296 
TEST_P(SparseHistogramTest,Serialize)297 TEST_P(SparseHistogramTest, Serialize) {
298   std::unique_ptr<SparseHistogram> histogram(NewSparseHistogram("Sparse"));
299   histogram->SetFlags(HistogramBase::kIPCSerializationSourceFlag);
300 
301   Pickle pickle;
302   histogram->SerializeInfo(&pickle);
303 
304   PickleIterator iter(pickle);
305 
306   int type;
307   EXPECT_TRUE(iter.ReadInt(&type));
308   EXPECT_EQ(SPARSE_HISTOGRAM, type);
309 
310   std::string name;
311   EXPECT_TRUE(iter.ReadString(&name));
312   EXPECT_EQ("Sparse", name);
313 
314   int flag;
315   EXPECT_TRUE(iter.ReadInt(&flag));
316   EXPECT_EQ(HistogramBase::kIPCSerializationSourceFlag, flag);
317 
318   // No more data in the pickle.
319   EXPECT_FALSE(iter.SkipBytes(1));
320 }
321 
322 // Ensure that race conditions that cause multiple, identical sparse histograms
323 // to be created will safely resolve to a single one.
TEST_P(SparseHistogramTest,DuplicationSafety)324 TEST_P(SparseHistogramTest, DuplicationSafety) {
325   const char histogram_name[] = "Duplicated";
326   size_t histogram_count = StatisticsRecorder::GetHistogramCount();
327 
328   // Create a histogram that we will later duplicate.
329   HistogramBase* original =
330       SparseHistogram::FactoryGet(histogram_name, HistogramBase::kNoFlags);
331   ++histogram_count;
332   DCHECK_EQ(histogram_count, StatisticsRecorder::GetHistogramCount());
333   original->Add(1);
334 
335   // Create a duplicate. This has to happen differently depending on where the
336   // memory is taken from.
337   if (use_persistent_histogram_allocator_) {
338     // To allocate from persistent memory, clear the last_created reference in
339     // the GlobalHistogramAllocator. This will cause an Import to recreate
340     // the just-created histogram which will then be released as a duplicate.
341     GlobalHistogramAllocator::Get()->ClearLastCreatedReferenceForTesting();
342     // Creating a different histogram will first do an Import to ensure it
343     // hasn't been created elsewhere, triggering the duplication and release.
344     SparseHistogram::FactoryGet("something.new", HistogramBase::kNoFlags);
345     ++histogram_count;
346   } else {
347     // To allocate from the heap, just call the (private) constructor directly.
348     // Delete it immediately like would have happened within FactoryGet();
349     std::unique_ptr<SparseHistogram> something =
350         NewSparseHistogram(histogram_name);
351     DCHECK_NE(original, something.get());
352   }
353   DCHECK_EQ(histogram_count, StatisticsRecorder::GetHistogramCount());
354 
355   // Re-creating the histogram via FactoryGet() will return the same one.
356   HistogramBase* duplicate =
357       SparseHistogram::FactoryGet(histogram_name, HistogramBase::kNoFlags);
358   DCHECK_EQ(original, duplicate);
359   DCHECK_EQ(histogram_count, StatisticsRecorder::GetHistogramCount());
360   duplicate->Add(2);
361 
362   // Ensure that original histograms are still cross-functional.
363   original->Add(2);
364   duplicate->Add(1);
365   std::unique_ptr<HistogramSamples> snapshot_orig = original->SnapshotSamples();
366   std::unique_ptr<HistogramSamples> snapshot_dup = duplicate->SnapshotSamples();
367   DCHECK_EQ(2, snapshot_orig->GetCount(2));
368   DCHECK_EQ(2, snapshot_dup->GetCount(1));
369 }
370 
TEST_P(SparseHistogramTest,FactoryTime)371 TEST_P(SparseHistogramTest, FactoryTime) {
372   const int kTestCreateCount = 1 << 10;  // Must be power-of-2.
373   const int kTestLookupCount = 100000;
374   const int kTestAddCount = 100000;
375 
376   // Create all histogram names in advance for accurate timing below.
377   std::vector<std::string> histogram_names;
378   for (int i = 0; i < kTestCreateCount; ++i) {
379     histogram_names.push_back(
380         StringPrintf("TestHistogram.%d", i % kTestCreateCount));
381   }
382 
383   // Calculate cost of creating histograms.
384   TimeTicks create_start = TimeTicks::Now();
385   for (int i = 0; i < kTestCreateCount; ++i)
386     SparseHistogram::FactoryGet(histogram_names[i], HistogramBase::kNoFlags);
387   TimeDelta create_ticks = TimeTicks::Now() - create_start;
388   int64_t create_ms = create_ticks.InMilliseconds();
389 
390   VLOG(1) << kTestCreateCount << " histogram creations took " << create_ms
391           << "ms or about " << (create_ms * 1000000) / kTestCreateCount
392           << "ns each.";
393 
394   // Calculate cost of looking up existing histograms.
395   TimeTicks lookup_start = TimeTicks::Now();
396   for (int i = 0; i < kTestLookupCount; ++i) {
397     // 6007 is co-prime with kTestCreateCount and so will do lookups in an
398     // order less likely to be cacheable (but still hit them all) should the
399     // underlying storage use the exact histogram name as the key.
400     const int i_mult = 6007;
401     static_assert(i_mult < INT_MAX / kTestCreateCount, "Multiplier too big");
402     int index = (i * i_mult) & (kTestCreateCount - 1);
403     SparseHistogram::FactoryGet(histogram_names[index],
404                                 HistogramBase::kNoFlags);
405   }
406   TimeDelta lookup_ticks = TimeTicks::Now() - lookup_start;
407   int64_t lookup_ms = lookup_ticks.InMilliseconds();
408 
409   VLOG(1) << kTestLookupCount << " histogram lookups took " << lookup_ms
410           << "ms or about " << (lookup_ms * 1000000) / kTestLookupCount
411           << "ns each.";
412 
413   // Calculate cost of accessing histograms.
414   HistogramBase* histogram =
415       SparseHistogram::FactoryGet(histogram_names[0], HistogramBase::kNoFlags);
416   ASSERT_TRUE(histogram);
417   TimeTicks add_start = TimeTicks::Now();
418   for (int i = 0; i < kTestAddCount; ++i)
419     histogram->Add(i & 127);
420   TimeDelta add_ticks = TimeTicks::Now() - add_start;
421   int64_t add_ms = add_ticks.InMilliseconds();
422 
423   VLOG(1) << kTestAddCount << " histogram adds took " << add_ms
424           << "ms or about " << (add_ms * 1000000) / kTestAddCount << "ns each.";
425 }
426 
TEST_P(SparseHistogramTest,ExtremeValues)427 TEST_P(SparseHistogramTest, ExtremeValues) {
428   static const struct {
429     Histogram::Sample sample;
430     int64_t expected_max;
431   } cases[] = {
432       // Note: We use -2147483647 - 1 rather than -2147483648 because the later
433       // is interpreted as - operator applied to 2147483648 and the latter can't
434       // be represented as an int32 and causes a warning.
435       {-2147483647 - 1, -2147483647LL},
436       {0, 1},
437       {2147483647, 2147483648LL},
438   };
439 
440   for (size_t i = 0; i < std::size(cases); ++i) {
441     HistogramBase* histogram =
442         SparseHistogram::FactoryGet(StringPrintf("ExtremeValues_%zu", i),
443                                     HistogramBase::kUmaTargetedHistogramFlag);
444     histogram->Add(cases[i].sample);
445 
446     std::unique_ptr<HistogramSamples> snapshot = histogram->SnapshotSamples();
447     std::unique_ptr<SampleCountIterator> it = snapshot->Iterator();
448     ASSERT_FALSE(it->Done());
449 
450     base::Histogram::Sample min;
451     int64_t max;
452     base::Histogram::Count count;
453     it->Get(&min, &max, &count);
454 
455     EXPECT_EQ(1, count);
456     EXPECT_EQ(cases[i].sample, min);
457     EXPECT_EQ(cases[i].expected_max, max);
458 
459     it->Next();
460     EXPECT_TRUE(it->Done());
461   }
462 }
463 
TEST_P(SparseHistogramTest,HistogramNameHash)464 TEST_P(SparseHistogramTest, HistogramNameHash) {
465   const char kName[] = "TestName";
466   HistogramBase* histogram = SparseHistogram::FactoryGet(
467       kName, HistogramBase::kUmaTargetedHistogramFlag);
468   EXPECT_EQ(histogram->name_hash(), HashMetricName(kName));
469 }
470 
TEST_P(SparseHistogramTest,CheckGetCountAndBucketData)471 TEST_P(SparseHistogramTest, CheckGetCountAndBucketData) {
472   std::unique_ptr<SparseHistogram> histogram(NewSparseHistogram("Sparse"));
473   // Add samples in reverse order and make sure the output is in correct order.
474   histogram->AddCount(/*sample=*/200, /*count=*/15);
475   histogram->AddCount(/*sample=*/100, /*count=*/5);
476   // Add samples to the same bucket and make sure they'll be aggregated.
477   histogram->AddCount(/*sample=*/100, /*count=*/5);
478 
479   const CountAndBucketData count_and_data_bucket =
480       GetCountAndBucketData(histogram.get());
481   EXPECT_EQ(25, count_and_data_bucket.count);
482   EXPECT_EQ(4000, count_and_data_bucket.sum);
483 
484   const base::Value::List& buckets_list = count_and_data_bucket.buckets;
485   ASSERT_EQ(2u, buckets_list.size());
486 
487   // Check the first bucket.
488   const base::Value::Dict* bucket1 = buckets_list[0].GetIfDict();
489   ASSERT_TRUE(bucket1 != nullptr);
490   EXPECT_EQ(bucket1->FindInt("low"), std::optional<int>(100));
491   EXPECT_EQ(bucket1->FindInt("high"), std::optional<int>(101));
492   EXPECT_EQ(bucket1->FindInt("count"), std::optional<int>(10));
493 
494   // Check the second bucket.
495   const base::Value::Dict* bucket2 = buckets_list[1].GetIfDict();
496   ASSERT_TRUE(bucket2 != nullptr);
497   EXPECT_EQ(bucket2->FindInt("low"), std::optional<int>(200));
498   EXPECT_EQ(bucket2->FindInt("high"), std::optional<int>(201));
499   EXPECT_EQ(bucket2->FindInt("count"), std::optional<int>(15));
500 }
501 
TEST_P(SparseHistogramTest,WriteAscii)502 TEST_P(SparseHistogramTest, WriteAscii) {
503   HistogramBase* histogram =
504       SparseHistogram::FactoryGet("AsciiOut", HistogramBase::kNoFlags);
505   histogram->AddCount(/*sample=*/4, /*count=*/5);
506   histogram->AddCount(/*sample=*/10, /*count=*/15);
507 
508   std::string output;
509   histogram->WriteAscii(&output);
510 
511   const char kOutputFormatRe[] =
512       R"(Histogram: AsciiOut recorded 20 samples.*\n)"
513       R"(4   -+O +\(5 = 25.0%\)\n)"
514       R"(10  -+O +\(15 = 75.0%\)\n)";
515 
516   EXPECT_THAT(output, testing::MatchesRegex(kOutputFormatRe));
517 }
518 
TEST_P(SparseHistogramTest,ToGraphDict)519 TEST_P(SparseHistogramTest, ToGraphDict) {
520   HistogramBase* histogram =
521       SparseHistogram::FactoryGet("HTMLOut", HistogramBase::kNoFlags);
522   histogram->AddCount(/*sample=*/4, /*count=*/5);
523   histogram->AddCount(/*sample=*/10, /*count=*/15);
524 
525   base::Value::Dict output = histogram->ToGraphDict();
526   std::string* header = output.FindString("header");
527   std::string* body = output.FindString("body");
528 
529   const char kOutputHeaderFormatRe[] =
530       R"(Histogram: HTMLOut recorded 20 samples.*)";
531   const char kOutputBodyFormatRe[] = R"(4   -+O +\(5 = 25.0%\)\n)"
532                                      R"(10  -+O +\(15 = 75.0%\)\n)";
533 
534   EXPECT_THAT(*header, testing::MatchesRegex(kOutputHeaderFormatRe));
535   EXPECT_THAT(*body, testing::MatchesRegex(kOutputBodyFormatRe));
536 }
537 
538 }  // namespace base
539