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