1 // Copyright 2018 The Chromium Authors. All rights reserved.
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 "components/tab_count_metrics/tab_count_metrics.h"
6
7 #include <algorithm>
8 #include <array>
9 #include <string>
10
11 #include "base/containers/flat_map.h"
12 #include "base/metrics/histogram.h"
13 #include "base/metrics/histogram_macros.h"
14 #include "base/test/metrics/histogram_tester.h"
15 #include "testing/gtest/include/gtest/gtest.h"
16
17 namespace tab_count_metrics {
18
19 namespace {
20
21 // For unit tests, we create bucketed test enumeration histograms with a prefix
22 // of |kTestMetricPrefix| and a range of [0, |kMaxTabCount|]. We want good
23 // test coverage in all buckets, but the overflow tab count bucket is
24 // unbounded, so we cap the max number of tabs at a reasonable value.
25 constexpr char kTestMetricPrefix[] = "TestMetric";
26 constexpr size_t kMaxTabCount = 400;
27
28 // TabCounts is a map of a tab count to expected histogram counts,
29 // which is used to validate the test histogram.
30 //
31 // When we record a sample for test metrics, the number of tabs is used as
32 // the bucket in the enumeration histogram. For example, if we record a sample
33 // with the number of tabs equal to 10, we use 10 as the sample in the
34 // enumeration histogram. Suppose we have TabCounts map, |expected_counts|.
35 // When recording the metric, we would increment the value of
36 // expected_counts[10]. |expected_counts| is then used to validate the
37 // histogram.
38 using TabCounts = base::flat_map<size_t, size_t>;
39
40 // Array of TabCounts, indexed by tab count bucket.
41 //
42 // Each value in this array corresponds to the expected counts for a different
43 // histogram, i.e. the histogram corresponding to the live tab count bucket
44 // equal to the array index. For example, given a
45 // HistogramCountsByTabCountBucket array, |expected_counts_by_bucket|,
46 // expected_counts_by_bucket[1] are the expected counts for the histogram
47 // corresponding to live tab count bucket 1.
48 using HistogramCountsByTabCountBucket =
49 std::array<TabCounts, kNumTabCountBuckets>;
50
51 } // namespace
52
53 class TabCountMetricsTest : public testing::Test {
54 public:
55 TabCountMetricsTest() = default;
56 ~TabCountMetricsTest() override = default;
57
RecordTabCountMetric(size_t tab_count)58 void RecordTabCountMetric(size_t tab_count) {
59 EXPECT_LE(tab_count, kMaxTabCount);
60 const size_t bucket = BucketForTabCount(tab_count);
61 STATIC_HISTOGRAM_POINTER_GROUP(
62 HistogramName(kTestMetricPrefix, /* live_tabs_only = */ true, bucket),
63 static_cast<int>(bucket), static_cast<int>(kNumTabCountBuckets),
64 Add(tab_count),
65 base::Histogram::FactoryGet(
66 HistogramName(kTestMetricPrefix, /* live_tabs_only = */ true,
67 bucket),
68 1, kMaxTabCount, kMaxTabCount + 1,
69 base::HistogramBase::kUmaTargetedHistogramFlag));
70 }
71
72 protected:
ValidateHistograms(const HistogramCountsByTabCountBucket & expected_counts)73 void ValidateHistograms(
74 const HistogramCountsByTabCountBucket& expected_counts) {
75 for (size_t bucket = 0; bucket < expected_counts.size(); bucket++) {
76 size_t expected_total_count = 0;
77 std::string histogram_name =
78 HistogramName(kTestMetricPrefix, /* live_tabs_only = */ true, bucket);
79 for (const auto& tab_counts : expected_counts[bucket]) {
80 histogram_tester_.ExpectBucketCount(histogram_name, tab_counts.first,
81 tab_counts.second);
82 expected_total_count += tab_counts.second;
83 }
84 histogram_tester_.ExpectTotalCount(histogram_name, expected_total_count);
85 }
86 }
87
88 private:
89 base::HistogramTester histogram_tester_;
90 };
91
TEST_F(TabCountMetricsTest,HistogramNames)92 TEST_F(TabCountMetricsTest, HistogramNames) {
93 // Testing with hard-coded strings to check that the concatenated names
94 // produced by HistogramName() are indeed what we expect. If the bucket ranges
95 // change, these strings will need to as well.
96 const std::string kTestMetricNameBucket1("TestMetric.ByLiveTabCount.1Tab");
97 const std::string kTestMetricNameBucket3(
98 "TestMetric.ByLiveTabCount.3To4Tabs");
99 EXPECT_EQ(kTestMetricNameBucket1,
100 HistogramName(kTestMetricPrefix, /* live_tabs_only = */ true, 1));
101 EXPECT_EQ(kTestMetricNameBucket3,
102 HistogramName(kTestMetricPrefix, /* live_tabs_only = */ true, 3));
103 }
104
TEST_F(TabCountMetricsTest,RecordMetricsForBucketLimits)105 TEST_F(TabCountMetricsTest, RecordMetricsForBucketLimits) {
106 // For each bucket, simulate recording metrics with the tab count equal
107 // to the lower and upper bounds of each bucket. For the last bucket, we use
108 // an artificial upper bound since that bucket has no practical bound (max
109 // size_t value) and do not want to create a histogram with that many buckets.
110 HistogramCountsByTabCountBucket expected_counts;
111 for (size_t bucket = 0; bucket < expected_counts.size(); bucket++) {
112 size_t num_tabs = internal::BucketMin(bucket);
113 RecordTabCountMetric(num_tabs);
114 // Expect a count of 1 for the |num_tabs| case in the histogram
115 // corresponding to |bucket|.
116 expected_counts[bucket].emplace(num_tabs, 1);
117
118 num_tabs = std::min(internal::BucketMax(bucket), kMaxTabCount);
119 RecordTabCountMetric(num_tabs);
120 // Ensure that we aren't setting the artificial maximum for the last bucket
121 // too low.
122 EXPECT_LE(internal::BucketMin(bucket), num_tabs);
123
124 auto iter = expected_counts[bucket].find(num_tabs);
125 if (iter != expected_counts[bucket].end()) {
126 // If the lower bound for |bucket| is the same as the upper bound, we've
127 // already recorded a value for |num_tabs| in |bucket|.
128 EXPECT_EQ(iter->second, 1u);
129 ++iter->second;
130 } else {
131 expected_counts[bucket].emplace(num_tabs, 1);
132 }
133
134 ValidateHistograms(expected_counts);
135 }
136 }
137
TEST_F(TabCountMetricsTest,RecordMetricsForAllBucketValues)138 TEST_F(TabCountMetricsTest, RecordMetricsForAllBucketValues) {
139 // For each bucket, simulate recording metrics with the tab count equal
140 // to each value in the bucket range, i.e. from BucketMin() to BucketMax().
141 // For the last bucket, we use an artificial upper bound since that bucket has
142 // no practical bound (max size_t value) and do not want to create a histogram
143 // with that many buckets.
144 HistogramCountsByTabCountBucket expected_counts;
145 for (size_t bucket = 0; bucket < expected_counts.size(); bucket++) {
146 size_t num_tabs_lower_bound = internal::BucketMin(bucket);
147 size_t num_tabs_upper_bound =
148 std::min(internal::BucketMax(bucket), kMaxTabCount);
149 EXPECT_LE(num_tabs_lower_bound, num_tabs_upper_bound);
150 for (size_t tab_count = num_tabs_lower_bound;
151 tab_count <= num_tabs_upper_bound; tab_count++) {
152 RecordTabCountMetric(tab_count);
153 expected_counts[bucket].emplace(tab_count, 1);
154 }
155 ValidateHistograms(expected_counts);
156 }
157 }
158
TEST_F(TabCountMetricsTest,BucketsDoNotOverlap)159 TEST_F(TabCountMetricsTest, BucketsDoNotOverlap) {
160 // For any number of tabs >= 0, the tab should belong to exactly one bucket.
161 for (size_t tab_count = 0; tab_count <= kMaxTabCount; tab_count++) {
162 bool has_bucket_for_tab_count = false;
163 for (size_t bucket = 0; bucket < kNumTabCountBuckets; bucket++) {
164 if (internal::IsInBucket(tab_count, bucket)) {
165 EXPECT_FALSE(has_bucket_for_tab_count);
166 has_bucket_for_tab_count = true;
167 }
168 }
169 EXPECT_TRUE(has_bucket_for_tab_count);
170 }
171 }
172
173 } // namespace tab_count_metrics
174