Commit a4f4244c authored by Scott Haseley's avatar Scott Haseley Committed by Commit Bot

[metrics] Adding helper methods for creating metrics bucketed by number of live tabs

This CL defines the live tab count buckets, adds a namespace and helper
methods for defining metrics bucketed by live tab count, and adds unit tests
that test example metrics. Actual metrics will be implemented in follow-up CLs.

Design doc: https://docs.google.com/document/d/1FB6Pv5Pu0jbYJwSfnqSeufHVOGEt9-d1agpzA4_CC-k/edit?usp=sharing

Bug: 861796
Change-Id: Idd510da5f3c40f9c4d5d1dc292fb6b4139217af2
Reviewed-on: https://chromium-review.googlesource.com/1166215
Commit-Queue: Scott Haseley <shaseley@google.com>
Reviewed-by: default avatarChris Hamilton <chrisha@chromium.org>
Reviewed-by: default avatarMark Pearson <mpearson@chromium.org>
Reviewed-by: default avatarJochen Eisinger <jochen@chromium.org>
Cr-Commit-Position: refs/heads/master@{#585672}
parent 2c7bd7f4
......@@ -114,6 +114,7 @@ test("components_unittests") {
"//components/language/core/common:unit_tests",
"//components/language_usage_metrics:unit_tests",
"//components/leveldb_proto:unit_tests",
"//components/live_tab_count_metrics:unit_tests",
"//components/login:unit_tests",
"//components/metrics:unit_tests",
"//components/navigation_metrics:unit_tests",
......
# Copyright 2018 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
component("live_tab_count_metrics") {
sources = [
"live_tab_count_metrics.cc",
"live_tab_count_metrics.h",
]
defines = [ "IS_LIVE_TAB_COUNT_METRICS_IMPL" ]
public_deps = [
"//base",
]
}
source_set("unit_tests") {
testonly = true
sources = [
"live_tab_count_metrics_unittest.cc",
]
deps = [
":live_tab_count_metrics",
"//base/test:test_support",
"//testing/gtest",
]
}
This directory contains process-independent code for recording metrics bucketed
by the number of live tabs.
We consider a tab to be alive (i.e. a live tab) if it is a UI tab (e.g. in a
tabstrip, as opposed to a prerenderer), and it has not been discarded and has
not crashed. Tabs can be discarded on desktop by TabManager to conserve
resources, and tabs crash when the corresponding renderer process is killed,
e.g. due to limited resources (OOM).
Clients of this component must be able to count live tabs. The interface this
component exposes provides a way to help create metrics bucketed by live tab
count in a consistent manner, but this is dependent on the client knowing the
count. The code in this directory is meant to be shared between processes, and
so we do not count live tabs here. The live tab count is a browser concept, and
it can be computed there. To record metrics bucketed by live tab counts in
processes other than the browser, the live tab count would need to be plumbed
out of the browser. In some cases this may be more efficient than plumbing
metrics data out of the process to the browser, which is an alternative.
This component should not have any dependencies other than //base as it
should be able to be used from any other place.
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/live_tab_count_metrics/live_tab_count_metrics.h"
#include <limits>
#include "base/logging.h"
#include "base/stl_util.h"
namespace live_tab_count_metrics {
// These values represent the lower bound for each bucket, and define the live
// tab count buckets. The final bucket has no upper bound, and each other
// bucket, i, is bounded above by the lower bound of bucket i + 1.
//
// The buckets were determined from the Tabs.MaxTabsInADay histogram,
// approximating the 25th, 50th, 75th, 95th, and 99th percentiles, but with the
// single and zero tab cases separated.
//
// If adding or removing a bucket, update |kNumLiveTabCountBuckets|,
// |kLiveTabCountBucketMins|, and |kLiveTabCountBucketNames|. If adding,
// removing, or changing bucket ranges, the existing metrics that use these
// functions for emitting histograms should be marked as obsolete, and new
// metrics should be created. This can be accomplished by versioning
// |kLiveTabCountBucketNames|, e.g. ".ByLiveTabCount2.0Tabs", etc., and
// updating the histogram suffixes section of histograms.xml, creating a new
// entry for the new suffixes and marking the old suffixes obsolete.
constexpr size_t kLiveTabCountBucketMins[] = {0, 1, 2, 3, 5, 8, 20, 40};
// Text for the live tab count portion of metric names. These need to be kept
// in sync with |kLiveTabCountBucketMins|.
constexpr const char* kLiveTabCountBucketNames[]{
".ByLiveTabCount.0Tabs", ".ByLiveTabCount.1Tab",
".ByLiveTabCount.2Tabs", ".ByLiveTabCount.3To4Tabs",
".ByLiveTabCount.5To7Tabs", ".ByLiveTabCount.8To19Tabs",
".ByLiveTabCount.20To39Tabs", ".ByLiveTabCount.40OrMoreTabs"};
std::string HistogramName(const std::string prefix, size_t bucket) {
static_assert(
base::size(kLiveTabCountBucketMins) == kNumLiveTabCountBuckets,
"kLiveTabCountBucketMins must have kNumLiveTabCountBuckets elements.");
static_assert(
base::size(kLiveTabCountBucketNames) == kNumLiveTabCountBuckets,
"kLiveTabCountBucketNames must have kNumLiveTabCountBuckets elements.");
DCHECK_LT(bucket, kNumLiveTabCountBuckets);
DCHECK(prefix.length());
return prefix + kLiveTabCountBucketNames[bucket];
}
size_t BucketForLiveTabCount(size_t num_live_tabs) {
for (size_t bucket = 0; bucket < kNumLiveTabCountBuckets; bucket++) {
if (internal::IsInBucket(num_live_tabs, bucket))
return bucket;
}
// There should be a bucket for any number of tabs >= 0.
NOTREACHED();
return kNumLiveTabCountBuckets;
}
namespace internal {
size_t BucketMin(size_t bucket) {
DCHECK_LT(bucket, kNumLiveTabCountBuckets);
return kLiveTabCountBucketMins[bucket];
}
size_t BucketMax(size_t bucket) {
DCHECK_LT(bucket, kNumLiveTabCountBuckets);
// The last bucket includes everything after the min bucket value.
if (bucket == kNumLiveTabCountBuckets - 1)
return std::numeric_limits<size_t>::max();
return kLiveTabCountBucketMins[bucket + 1] - 1;
}
bool IsInBucket(size_t num_live_tabs, size_t bucket) {
DCHECK_LT(bucket, kNumLiveTabCountBuckets);
return num_live_tabs >= BucketMin(bucket) &&
num_live_tabs <= BucketMax(bucket);
}
} // namespace internal
} // namespace live_tab_count_metrics
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_LIVE_TAB_COUNT_METRICS_LIVE_TAB_COUNT_METRICS_H_
#define COMPONENTS_LIVE_TAB_COUNT_METRICS_LIVE_TAB_COUNT_METRICS_H_
#include <string>
#include "base/component_export.h"
// This namespace contains functions for creating histograms bucketed by number
// of live tabs.
//
// All bucket parameters --- number of buckets, bucket sizes, bucket names ---
// are determined at compile time, and these methods are safe to call from any
// thread.
//
// A typical example of creating a histogram bucketed by live tab count using
// STATIC_HISTOGRAM_POINTER_GROUP looks something like this:
// const size_t live_tab_count = GetLiveTabCount();
// const size_t bucket =
// live_tab_count_metrics::BucketForLiveTabCount(live_tab_count);
// STATIC_HISTOGRAM_POINTER_GROUP(
// live_tab_count_metrics::HistogramName(constant_histogram_prefix,
// bucket),
// static_cast<int>(bucket),
// static_cast<int>(live_tab_count_metrics::kNumLiveTabCountBuckets),
// Add(sample),
// base::Histogram::FactoryGet(
// live_tab_count_metrics::HistogramName(constant_histogram_prefix,
// bucket),
// MINIMUM_SAMPLE, MAXIMUM_SAMPLE, BUCKET_COUNT,
// base::HistogramBase::kUmaTargetedHistogramFlag));
// }
namespace live_tab_count_metrics {
// |kNumLiveTabCountBuckets| is used in various constexpr arrays and as a bound
// on the histogram array when using STATIC_HISTOGRAM_POINTER_GROUP. This value
// must be equal to the length of the array of live tab count bucket min values
// (|kLiveTabCountBucketMins|) and the array of bucket names
// (|kLiveTabCountBucketNames|) found in the corresponding .cc file.
constexpr size_t kNumLiveTabCountBuckets = 8;
// Returns the histogram name for |bucket|. The histogram name is the
// concatenation of |prefix| and the name corresponding to |bucket|, which is of
// the form |prefix| + ".ByLiveTabCount." + <BucketRangeText>, where
// <BucketRangeText> is a string describing the bucket range, e.g. "1Tab",
// "3To4Tabs", etc. See |kLiveTabCountBucketNames| for all of the bucket names.
// |bucket| must be in the interval [0, |kNumLiveTabCountBuckets|).
COMPONENT_EXPORT(LIVE_TAB_COUNT_METRICS)
std::string HistogramName(const std::string prefix, size_t bucket);
// Return the bucket index for the |num_live_tabs|.
COMPONENT_EXPORT(LIVE_TAB_COUNT_METRICS)
size_t BucketForLiveTabCount(size_t num_live_tabs);
// These are exposed for unit tests.
namespace internal {
// Returns the number of tabs corresponding to the minimum value of |bucket|.
// |bucket| must be in the interval [0, |kNumLiveTabCountBuckets|).
COMPONENT_EXPORT(LIVE_TAB_COUNT_METRICS)
size_t BucketMin(size_t bucket);
// Returns the number of tabs corresponding to the maximum value of |bucket|.
// |bucket| must be in the interval [0, |kNumLiveTabCountBuckets|).
COMPONENT_EXPORT(LIVE_TAB_COUNT_METRICS)
size_t BucketMax(size_t bucket);
// Returns true if |num_live_tabs| falls within |bucket|.
// |bucket| must be in the interval [0, |kNumLiveTabCountBuckets|).
COMPONENT_EXPORT(LIVE_TAB_COUNT_METRICS)
bool IsInBucket(size_t num_live_tabs, size_t bucket);
} // namespace internal
} // namespace live_tab_count_metrics
#endif // COMPONENTS_LIVE_TAB_COUNT_METRICS_LIVE_TAB_COUNT_METRICS_H_
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/live_tab_count_metrics/live_tab_count_metrics.h"
#include <algorithm>
#include <array>
#include <string>
#include "base/containers/flat_map.h"
#include "base/metrics/histogram.h"
#include "base/metrics/histogram_macros.h"
#include "base/test/metrics/histogram_tester.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace live_tab_count_metrics {
namespace {
// For unit tests, we create bucketed test enumeration histograms with a prefix
// of |kTestMetricPrefix| and a range of [0, |kMaxLiveTabCount|]. We want good
// test coverage in all buckets, but the overflow live tab count bucket is
// unbounded, so we cap the max number of live tabs at a reasonable value.
constexpr char kTestMetricPrefix[] = "TestMetric";
constexpr size_t kMaxLiveTabCount = 400;
// LiveTabCounts is a map of a live tab count to expected histogram counts,
// which is used to validate the test histogram.
//
// When we record a sample for test metrics, the number of live tabs is used as
// the bucket in the enumeration histogram. For example, if we record a sample
// with the number of live tabs equal to 10, we use 10 as the sample in the
// enumeration histogram. Suppose we have LiveTabCounts map, |expected_counts|.
// When recording the metric, we would increment the value of
// expected_counts[10]. |expected_counts| is then used to validate the
// histogram.
using LiveTabCounts = base::flat_map<size_t, size_t>;
// Array of LiveTabCounts, indexed by live tab count bucket.
//
// Each value in this array corresponds to the expected counts for a different
// histogram, i.e. the histogram corresponding to the live tab count bucket
// equal to the array index. For example, given a
// HistogramCountsByLiveTabCountBucket array, |expected_counts_by_bucket|,
// expected_counts_by_bucket[1] are the expected counts for the histogram
// corresponding to live tab count bucket 1.
using HistogramCountsByLiveTabCountBucket =
std::array<LiveTabCounts, kNumLiveTabCountBuckets>;
} // namespace
class LiveTabCountMetricsTest : public testing::Test {
public:
LiveTabCountMetricsTest() = default;
~LiveTabCountMetricsTest() override = default;
void RecordLiveTabCountMetric(size_t live_tab_count) {
EXPECT_LE(live_tab_count, kMaxLiveTabCount);
const size_t bucket = BucketForLiveTabCount(live_tab_count);
STATIC_HISTOGRAM_POINTER_GROUP(
HistogramName(kTestMetricPrefix, bucket), static_cast<int>(bucket),
static_cast<int>(kNumLiveTabCountBuckets), Add(live_tab_count),
base::Histogram::FactoryGet(
HistogramName(kTestMetricPrefix, bucket), 1, kMaxLiveTabCount,
kMaxLiveTabCount + 1,
base::HistogramBase::kUmaTargetedHistogramFlag));
}
protected:
void ValidateHistograms(
const HistogramCountsByLiveTabCountBucket& expected_counts) {
for (size_t bucket = 0; bucket < expected_counts.size(); bucket++) {
size_t expected_total_count = 0;
std::string histogram_name = HistogramName(kTestMetricPrefix, bucket);
for (const auto& live_tab_counts : expected_counts[bucket]) {
histogram_tester_.ExpectBucketCount(
histogram_name, live_tab_counts.first, live_tab_counts.second);
expected_total_count += live_tab_counts.second;
}
histogram_tester_.ExpectTotalCount(histogram_name, expected_total_count);
}
}
private:
base::HistogramTester histogram_tester_;
};
TEST_F(LiveTabCountMetricsTest, HistogramNames) {
// Testing with hard-coded strings to check that the concatenated names
// produced by HistogramName() are indeed what we expect. If the bucket ranges
// change, these strings will need to as well.
const std::string kTestMetricNameBucket1("TestMetric.ByLiveTabCount.1Tab");
const std::string kTestMetricNameBucket3(
"TestMetric.ByLiveTabCount.3To4Tabs");
EXPECT_EQ(kTestMetricNameBucket1, HistogramName(kTestMetricPrefix, 1));
EXPECT_EQ(kTestMetricNameBucket3, HistogramName(kTestMetricPrefix, 3));
}
TEST_F(LiveTabCountMetricsTest, RecordMetricsForBucketLimits) {
// For each bucket, simulate recording metrics with the live tab count equal
// to the lower and upper bounds of each bucket. For the last bucket, we use
// an artificial upper bound since that bucket has no practical bound (max
// size_t value) and do not want to create a histogram with that many buckets.
HistogramCountsByLiveTabCountBucket expected_counts;
for (size_t bucket = 0; bucket < expected_counts.size(); bucket++) {
size_t num_live_tabs = internal::BucketMin(bucket);
RecordLiveTabCountMetric(num_live_tabs);
// Expect a count of 1 for the |num_live_tabs| case in the histogram
// corresponding to |bucket|.
expected_counts[bucket].emplace(num_live_tabs, 1);
num_live_tabs = std::min(internal::BucketMax(bucket), kMaxLiveTabCount);
RecordLiveTabCountMetric(num_live_tabs);
// Ensure that we aren't setting the artificial maximum for the last bucket
// too low.
EXPECT_LE(internal::BucketMin(bucket), num_live_tabs);
auto iter = expected_counts[bucket].find(num_live_tabs);
if (iter != expected_counts[bucket].end()) {
// If the lower bound for |bucket| is the same as the upper bound, we've
// already recorded a value for |num_live_tabs| in |bucket|.
EXPECT_EQ(iter->second, 1u);
++iter->second;
} else {
expected_counts[bucket].emplace(num_live_tabs, 1);
}
ValidateHistograms(expected_counts);
}
}
TEST_F(LiveTabCountMetricsTest, RecordMetricsForAllBucketValues) {
// For each bucket, simulate recording metrics with the live tab count equal
// to each value in the bucket range, i.e. from BucketMin() to BucketMax().
// For the last bucket, we use an artificial upper bound since that bucket has
// no practical bound (max size_t value) and do not want to create a histogram
// with that many buckets.
HistogramCountsByLiveTabCountBucket expected_counts;
for (size_t bucket = 0; bucket < expected_counts.size(); bucket++) {
size_t num_live_tabs_lower_bound = internal::BucketMin(bucket);
size_t num_live_tabs_upper_bound =
std::min(internal::BucketMax(bucket), kMaxLiveTabCount);
EXPECT_LE(num_live_tabs_lower_bound, num_live_tabs_upper_bound);
for (size_t live_tab_count = num_live_tabs_lower_bound;
live_tab_count <= num_live_tabs_upper_bound; live_tab_count++) {
RecordLiveTabCountMetric(live_tab_count);
expected_counts[bucket].emplace(live_tab_count, 1);
}
ValidateHistograms(expected_counts);
}
}
TEST_F(LiveTabCountMetricsTest, BucketsDoNotOverlap) {
// For any number of tabs >= 0, the tab should belong to exactly one bucket.
for (size_t tab_count = 0; tab_count <= kMaxLiveTabCount; tab_count++) {
bool has_bucket_for_tab_count = false;
for (size_t bucket = 0; bucket < kNumLiveTabCountBuckets; bucket++) {
if (internal::IsInBucket(tab_count, bucket)) {
EXPECT_FALSE(has_bucket_for_tab_count);
has_bucket_for_tab_count = true;
}
}
EXPECT_TRUE(has_bucket_for_tab_count);
}
}
} // namespace live_tab_count_metrics
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment