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