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