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