blob: 369b978f961c40cf6f323bbe727f41e794d10e83 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cc/metrics/scroll_jank_v4_processor.h"
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <variant>
#include "base/strings/to_string.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "cc/base/features.h"
#include "cc/metrics/event_metrics.h"
#include "cc/test/event_metrics_test_creator.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cc {
namespace {
using ::testing::ElementsAre;
constexpr base::TimeDelta kVsyncInterval = base::Milliseconds(16);
constexpr base::TimeTicks MillisSinceEpoch(int64_t millis) {
return base::TimeTicks() + base::Milliseconds(millis);
}
constexpr JankReasonArray<int> MakeMissedVsyncCounts(
std::initializer_list<std::pair<JankReason, int>> values) {
JankReasonArray<int> result = {}; // Default initialize to 0
for (const auto& [reason, missed_vsyncs] : values) {
result[static_cast<int>(reason)] += missed_vsyncs;
}
return result;
}
// Matches a pointer to event metrics iff
// `result_matcher` matches `event_metrics->AsScrollUpdate()->scroll_jank_v4()`.
::testing::Matcher<const std::unique_ptr<EventMetrics>&>
ScrollJankV4DataMatches(
::testing::Matcher<
const std::optional<ScrollUpdateEventMetrics::ScrollJankV4Result>&>
result_matcher) {
// We need to wrap `->AsScrollUpdate()` in a `::testing::Pointee()` because
// `event_metrics` is a `std::unique_ptr`.
return ::testing::Pointee(::testing::Property(
&EventMetrics::AsScrollUpdate,
// We don't need to wrap `->scroll_jank_v4` in a `::testing::Pointee()`
// because `event_metrics->AsScrollUpdate()` is a raw pointer.
Property(&ScrollUpdateEventMetrics::scroll_jank_v4, result_matcher)));
}
// Matches a pointer to event metrics iff
// `event_metrics->AsScrollUpdate()->scroll_jank_v4()` is empty.
const ::testing::Matcher<const std::unique_ptr<EventMetrics>&> kNullJankV4Data =
ScrollJankV4DataMatches(::testing::Eq(std::nullopt));
// Matches a pointer to event metrics iff
// `missed_vsyncs_per_reason_matcher` matches
// `event_metrics->AsScrollUpdate()->scroll_jank_v4().missed_vsyncs_per_reason`.
::testing::Matcher<const std::unique_ptr<EventMetrics>&>
HasMissedVsyncsPerReasonV4(::testing::Matcher<const JankReasonArray<int>&>
missed_vsyncs_per_reason_matcher) {
return ScrollJankV4DataMatches(::testing::Optional(::testing::Field(
&ScrollUpdateEventMetrics::ScrollJankV4Result::missed_vsyncs_per_reason,
missed_vsyncs_per_reason_matcher)));
}
// Matches a pointer to event metrics iff
// `event_metrics->AsScrollUpdate()->scroll_jank_v4().missed_vsyncs_per_reason`
// contains only zeros.
const ::testing::Matcher<const std::unique_ptr<EventMetrics>&> kIsNotJankyV4 =
HasMissedVsyncsPerReasonV4(JankReasonArray<int>{});
// Matches a pointer to event metrics iff, for each jank reason,
// `event_metrics->AsScrollUpdate()->scroll_jank_v4().missed_vsyncs_per_reason`
// matches the `reason_to_missed_vsync_count` mapping (and is otherwise zero).
::testing::Matcher<const std::unique_ptr<EventMetrics>&>
IsJankyV4WithMissedVsyncCounts(std::initializer_list<std::pair<JankReason, int>>
reason_to_missed_vsync_count) {
return HasMissedVsyncsPerReasonV4(
MakeMissedVsyncCounts(reason_to_missed_vsync_count));
}
// Variants of the `features::kHandleNonDamagingInputsInScrollJankV4Metric`
// feature and its configuration.
enum class TestVariant {
// Legacy behavior where `ScrollJankV4Processor` will ignore non-damaging
// inputs (similarly to the scroll jank v1 metric).
//
// Disables `features::kHandleNonDamagingInputsInScrollJankV4Metric`.
kLegacyBehavior,
// New behavior where `ScrollJankV4Processor` will reconstruct a timeline of
// non-damaging and damaging frames for the purposes of evaluating scroll
// jank. `ScrollJankV4HistogramEmitter` will emit fixed window UMA
// histograms after each window of 64 damaging frames.
//
// Enables `features::kHandleNonDamagingInputsInScrollJankV4Metric` with
// `feature::kCountNonDamagingFramesTowardsHistogramFrameCount` set to false.
kNewBehaviorCountDamagingFramesOnly,
// New behavior where `ScrollJankV4Processor` will reconstruct a timeline of
// non-damaging and damaging frames for the purposes of evaluating scroll
// jank. `ScrollJankV4HistogramEmitter` will emit fixed window UMA
// histograms after each window of 64 frames (both damaging and non-damaging).
//
// Enables `features::kHandleNonDamagingInputsInScrollJankV4Metric` with
// `feature::kCountNonDamagingFramesTowardsHistogramFrameCount` set to true.
kNewBehaviorCountAllFrames
};
struct ScrollJankV4ProcessorTestCase {
TestVariant variant;
std::string test_name;
};
} // namespace
// Unit test of `ScrollJankV4Processor` parameterized by the
// `features::kHandleNonDamagingInputsInScrollJankV4Metric` feature and its
// configuration. Each of the test cases represents a possible scenario of
// input→frame delivery. The test cases document how the handling of
// non-damaging inputs and frames differs based on the above feature.
class ScrollJankV4ProcessorTest
: public testing::TestWithParam<ScrollJankV4ProcessorTestCase> {
public:
void SetUp() override {
switch (GetParam().variant) {
case TestVariant::kLegacyBehavior:
scoped_feature_list_.InitAndDisableFeature(
features::kHandleNonDamagingInputsInScrollJankV4Metric);
break;
case TestVariant::kNewBehaviorCountDamagingFramesOnly:
scoped_feature_list_.InitAndEnableFeatureWithParameters(
features::kHandleNonDamagingInputsInScrollJankV4Metric,
{{features::kCountNonDamagingFramesTowardsHistogramFrameCount.name,
base::ToString(false)}});
break;
case TestVariant::kNewBehaviorCountAllFrames:
scoped_feature_list_.InitAndEnableFeatureWithParameters(
features::kHandleNonDamagingInputsInScrollJankV4Metric,
{{features::kCountNonDamagingFramesTowardsHistogramFrameCount.name,
base::ToString(true)}});
break;
}
}
protected:
void AdvanceByVsyncs(int vsyncs) {
base::TimeDelta offset = vsyncs * kVsyncInterval;
next_input_generation_ts_ += offset;
next_begin_frame_ts_ += offset;
next_presentation_ts_ += offset;
}
viz::BeginFrameArgs CreateNextBeginFrameArgs() {
return viz::BeginFrameArgs::Create(
BEGINFRAME_FROM_HERE, /* source_id= */ 1,
next_begin_frame_sequence_id_++,
/* frame_time= */ next_begin_frame_ts_,
/* deadline= */ next_begin_frame_ts_ + kVsyncInterval / 3,
kVsyncInterval, viz::BeginFrameArgs::BeginFrameArgsType::NORMAL);
}
base::test::ScopedFeatureList scoped_feature_list_;
base::TimeTicks next_input_generation_ts_ = MillisSinceEpoch(4);
base::TimeTicks next_begin_frame_ts_ = MillisSinceEpoch(16);
base::TimeTicks next_presentation_ts_ = MillisSinceEpoch(32);
int next_begin_frame_sequence_id_ = 1;
EventMetricsTestCreator metrics_creator_;
ScrollJankV4Processor processor_;
};
/*
Tests that, regardless of `TestVariant`, the scroll jank v4 metric doesn't mark
frame production with consistent input delivery where each frame contains a
damaging scroll update as janky.
*/
TEST_P(ScrollJankV4ProcessorTest, ConsistentDamagingFrameProduction) {
// Start a scroll and present frames 1-64.
{
base::HistogramTester histogram_tester;
// Start with a regular scroll with two inputs per frame.
{
viz::BeginFrameArgs args = CreateNextBeginFrameArgs();
EventMetrics::List first_metrics;
first_metrics.push_back(metrics_creator_.CreateFirstGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
first_metrics.push_back(metrics_creator_.CreateFirstGestureScrollUpdate(
{.timestamp = next_input_generation_ts_ + kVsyncInterval / 2,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
processor_.ProcessEventsMetricsForPresentedFrame(
first_metrics, next_presentation_ts_, args);
EXPECT_THAT(first_metrics, ElementsAre(kIsNotJankyV4, kNullJankV4Data));
}
for (int i = 2; i <= 50; i++) {
AdvanceByVsyncs(1);
viz::BeginFrameArgs args = CreateNextBeginFrameArgs();
EventMetrics::List metrics;
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_ + kVsyncInterval / 2,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
processor_.ProcessEventsMetricsForPresentedFrame(
metrics, next_presentation_ts_, args);
EXPECT_THAT(metrics, ElementsAre(kIsNotJankyV4, kNullJankV4Data));
}
// Switch to a fling with one input per frame.
for (int i = 51; i <= 64; i++) {
AdvanceByVsyncs(1);
viz::BeginFrameArgs args = CreateNextBeginFrameArgs();
EventMetrics::List metrics;
metrics.push_back(metrics_creator_.CreateInertialGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 2.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
processor_.ProcessEventsMetricsForPresentedFrame(
metrics, next_presentation_ts_, args);
EXPECT_THAT(metrics, ElementsAre(kIsNotJankyV4));
}
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.FixedWindow", 0);
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.PerScroll", 0);
}
// Present frame 65 (end of first fixed window).
{
base::HistogramTester histogram_tester;
AdvanceByVsyncs(1);
viz::BeginFrameArgs args = CreateNextBeginFrameArgs();
EventMetrics::List last_metrics_in_fixed_window;
last_metrics_in_fixed_window.push_back(
metrics_creator_.CreateInertialGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 2.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
processor_.ProcessEventsMetricsForPresentedFrame(
last_metrics_in_fixed_window, next_presentation_ts_, args);
EXPECT_THAT(last_metrics_in_fixed_window, ElementsAre(kIsNotJankyV4));
histogram_tester.ExpectUniqueSample(
"Event.ScrollJank.DelayedFramesPercentage4.FixedWindow", 0, 1);
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.PerScroll", 0);
}
// Present 35 more frames.
{
base::HistogramTester histogram_tester;
for (int i = 66; i <= 100; i++) {
AdvanceByVsyncs(1);
viz::BeginFrameArgs args = CreateNextBeginFrameArgs();
EventMetrics::List metrics;
metrics.push_back(metrics_creator_.CreateInertialGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 2.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
processor_.ProcessEventsMetricsForPresentedFrame(
metrics, next_presentation_ts_, args);
EXPECT_THAT(metrics, ElementsAre(kIsNotJankyV4));
}
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.FixedWindow", 0);
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.PerScroll", 0);
}
// Finally, end the scroll.
{
base::HistogramTester histogram_tester;
AdvanceByVsyncs(1);
viz::BeginFrameArgs args = CreateNextBeginFrameArgs();
EventMetrics::List end_metrics;
end_metrics.push_back(metrics_creator_.CreateInertialGestureScrollEnd(
{.timestamp = next_input_generation_ts_,
.caused_frame_update = false,
.begin_frame_args = args}));
processor_.ProcessEventsMetricsForPresentedFrame(
end_metrics, next_presentation_ts_, args);
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.FixedWindow", 0);
histogram_tester.ExpectUniqueSample(
"Event.ScrollJank.DelayedFramesPercentage4.PerScroll", 0, 1);
}
}
/*
Tests that, regardless of `TestVariant`, the scroll jank v4 metric marks
"hiccups" in frame production with inconsistent damaging input delivery where
each frame contains a damaging scroll update as janky.
*/
TEST_P(ScrollJankV4ProcessorTest, InconsistentDamagingFrameProduction) {
// Start a scroll and present frames 1-64.
{
base::HistogramTester histogram_tester;
// Start with a regular scroll with two inputs per frame.
{
viz::BeginFrameArgs args = CreateNextBeginFrameArgs();
EventMetrics::List first_metrics;
first_metrics.push_back(metrics_creator_.CreateFirstGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
first_metrics.push_back(metrics_creator_.CreateFirstGestureScrollUpdate(
{.timestamp = next_input_generation_ts_ + kVsyncInterval / 2,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
processor_.ProcessEventsMetricsForPresentedFrame(
first_metrics, next_presentation_ts_, args);
EXPECT_THAT(first_metrics, ElementsAre(kIsNotJankyV4, kNullJankV4Data));
}
for (int i = 2; i <= 10; i++) {
AdvanceByVsyncs(1);
viz::BeginFrameArgs args = CreateNextBeginFrameArgs();
EventMetrics::List metrics;
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_ + kVsyncInterval / 2,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
processor_.ProcessEventsMetricsForPresentedFrame(
metrics, next_presentation_ts_, args);
EXPECT_THAT(metrics, ElementsAre(kIsNotJankyV4, kNullJankV4Data));
}
{
// The processor should mark frame 11 as janky:
// 1. It violates the running consistency rule because the first input
// should have been presented 1 VSync earlier (based on Chrome's past
// performance).
// 2. It violates the fast scroll continuity rule because there's more
// than 1 VSync between two consecutive presented frames containing
// inputs.
AdvanceByVsyncs(3);
viz::BeginFrameArgs args = CreateNextBeginFrameArgs();
EventMetrics::List metrics;
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_ - kVsyncInterval / 2,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_ + kVsyncInterval / 2,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
processor_.ProcessEventsMetricsForPresentedFrame(
metrics, next_presentation_ts_, args);
EXPECT_THAT(
metrics,
ElementsAre(
IsJankyV4WithMissedVsyncCounts(
{{JankReason::kMissedVsyncDueToDeceleratingInputFrameDelivery,
1},
{JankReason::kMissedVsyncDuringFastScroll, 2}}),
kNullJankV4Data, kNullJankV4Data));
}
for (int i = 12; i <= 50; i++) {
AdvanceByVsyncs(1);
viz::BeginFrameArgs args = CreateNextBeginFrameArgs();
EventMetrics::List metrics;
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_ + kVsyncInterval / 2,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
processor_.ProcessEventsMetricsForPresentedFrame(
metrics, next_presentation_ts_, args);
EXPECT_THAT(metrics, ElementsAre(kIsNotJankyV4, kNullJankV4Data));
}
// Switch to a fling with one input per frame.
{
// The processor should mark frame 51 as janky. It violates the fling
// continuity rule because Chrome missed 5 VSyncs at the transition from a
// fast regular scroll to a fast fling as janky.
AdvanceByVsyncs(6);
viz::BeginFrameArgs args = CreateNextBeginFrameArgs();
EventMetrics::List metrics;
metrics.push_back(metrics_creator_.CreateInertialGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 2.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
processor_.ProcessEventsMetricsForPresentedFrame(
metrics, next_presentation_ts_, args);
EXPECT_THAT(metrics, ElementsAre(IsJankyV4WithMissedVsyncCounts(
{{JankReason::kMissedVsyncAtStartOfFling, 5}})));
}
for (int i = 52; i <= 64; i++) {
AdvanceByVsyncs(1);
viz::BeginFrameArgs args = CreateNextBeginFrameArgs();
EventMetrics::List metrics;
metrics.push_back(metrics_creator_.CreateInertialGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 2.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
processor_.ProcessEventsMetricsForPresentedFrame(
metrics, next_presentation_ts_, args);
EXPECT_THAT(metrics, ElementsAre(kIsNotJankyV4));
}
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.FixedWindow", 0);
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.PerScroll", 0);
}
// Present frame 65 (end of first fixed window).
{
base::HistogramTester histogram_tester;
AdvanceByVsyncs(1);
viz::BeginFrameArgs args = CreateNextBeginFrameArgs();
EventMetrics::List last_metrics_in_fixed_window;
last_metrics_in_fixed_window.push_back(
metrics_creator_.CreateInertialGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 2.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
processor_.ProcessEventsMetricsForPresentedFrame(
last_metrics_in_fixed_window, next_presentation_ts_, args);
EXPECT_THAT(last_metrics_in_fixed_window, ElementsAre(kIsNotJankyV4));
histogram_tester.ExpectUniqueSample(
"Event.ScrollJank.DelayedFramesPercentage4.FixedWindow", 2 * 100 / 64,
1);
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.PerScroll", 0);
}
// Present 35 more frames.
{
base::HistogramTester histogram_tester;
for (int i = 66; i <= 80; i++) {
AdvanceByVsyncs(1);
viz::BeginFrameArgs args = CreateNextBeginFrameArgs();
EventMetrics::List metrics;
metrics.push_back(metrics_creator_.CreateInertialGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 2.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
processor_.ProcessEventsMetricsForPresentedFrame(
metrics, next_presentation_ts_, args);
EXPECT_THAT(metrics, ElementsAre(kIsNotJankyV4));
}
// The processor should mark frame 81 as janky. It violates the fling
// continuity rule because Chrome missed 9 VSyncs in the middle of a fast
// fling.
{
AdvanceByVsyncs(10);
viz::BeginFrameArgs args = CreateNextBeginFrameArgs();
EventMetrics::List metrics;
metrics.push_back(metrics_creator_.CreateInertialGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 2.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
processor_.ProcessEventsMetricsForPresentedFrame(
metrics, next_presentation_ts_, args);
EXPECT_THAT(metrics, ElementsAre(IsJankyV4WithMissedVsyncCounts(
{{JankReason::kMissedVsyncDuringFling, 9}})));
}
for (int i = 82; i <= 100; i++) {
AdvanceByVsyncs(1);
viz::BeginFrameArgs args = CreateNextBeginFrameArgs();
EventMetrics::List metrics;
metrics.push_back(metrics_creator_.CreateInertialGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 2.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
processor_.ProcessEventsMetricsForPresentedFrame(
metrics, next_presentation_ts_, args);
EXPECT_THAT(metrics, ElementsAre(kIsNotJankyV4));
}
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.FixedWindow", 0);
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.PerScroll", 0);
}
// Finally, end the scroll.
{
base::HistogramTester histogram_tester;
AdvanceByVsyncs(1);
viz::BeginFrameArgs args = CreateNextBeginFrameArgs();
EventMetrics::List end_metrics;
end_metrics.push_back(metrics_creator_.CreateInertialGestureScrollEnd(
{.timestamp = next_input_generation_ts_,
.caused_frame_update = false,
.begin_frame_args = args}));
processor_.ProcessEventsMetricsForPresentedFrame(
end_metrics, next_presentation_ts_, args);
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.FixedWindow", 0);
histogram_tester.ExpectUniqueSample(
"Event.ScrollJank.DelayedFramesPercentage4.PerScroll", 3 * 100 / 100,
1);
}
}
/*
Tests the behavior of the scroll jank v4 metric on consistent input delivery
with both damaging and non-damaging frames.
Both `TestVariant::kNewBehaviorCountDamagingFramesOnly` and
`TestVariant::kNewBehaviorCountAllFrames` should correctly mark all frames as
non-janky.
`TestVariant::kLegacyBehavior` yields several false positives because it ignores
non-damaging inputs.
*/
TEST_P(ScrollJankV4ProcessorTest, ConsistentMixedFrameProduction) {
// Start with a regular scroll with two inputs per frame.
{
base::HistogramTester histogram_tester;
{
viz::BeginFrameArgs args = CreateNextBeginFrameArgs();
EventMetrics::List first_metrics;
first_metrics.push_back(metrics_creator_.CreateFirstGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
first_metrics.push_back(metrics_creator_.CreateFirstGestureScrollUpdate(
{.timestamp = next_input_generation_ts_ + kVsyncInterval / 2,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
processor_.ProcessEventsMetricsForPresentedFrame(
first_metrics, next_presentation_ts_, args);
EXPECT_THAT(first_metrics, ElementsAre(kIsNotJankyV4, kNullJankV4Data));
}
// Frames presented: 1 damaging, 1 total.
// Interleave damaging and non-damaging frames.
for (int damaging_frame = 2; damaging_frame <= 31; damaging_frame++) {
AdvanceByVsyncs(1);
viz::BeginFrameArgs non_damaging_args = CreateNextBeginFrameArgs();
AdvanceByVsyncs(1);
viz::BeginFrameArgs damaging_args = CreateNextBeginFrameArgs();
EventMetrics::List metrics;
// Two inputs for a non-damaging frame.
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_ - kVsyncInterval,
.delta = 5.0f,
.caused_frame_update = false,
.did_scroll = false,
.begin_frame_args = non_damaging_args}));
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_ - kVsyncInterval / 2,
.delta = 5.0f,
.caused_frame_update = false,
.did_scroll = false,
.begin_frame_args = non_damaging_args}));
// Two inputs for a presented damaging frame.
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = damaging_args}));
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_ + kVsyncInterval / 2,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = damaging_args}));
processor_.ProcessEventsMetricsForPresentedFrame(
metrics, next_presentation_ts_, damaging_args);
switch (GetParam().variant) {
case TestVariant::kLegacyBehavior:
// The legacy behavior ignores `metrics[0]` (because it's
// non-damaging) and marks `metrics[2]` as janky due to the fast
// scroll rule (because the metric sees a missed VSync between two
// consecutive damaging frames).
EXPECT_THAT(
metrics,
ElementsAre(kNullJankV4Data, kNullJankV4Data,
IsJankyV4WithMissedVsyncCounts(
{{JankReason::kMissedVsyncDuringFastScroll, 1}}),
kNullJankV4Data));
break;
case TestVariant::kNewBehaviorCountDamagingFramesOnly:
case TestVariant::kNewBehaviorCountAllFrames:
// The new behavior identifies one non-damaging frame (starting with
// `metrics[0]`) and one damaging frame (starting with `metrics[2]`).
// It doesn't observe any missed VSyncs, so it doesn't mark any frame
// as janky.
EXPECT_THAT(metrics, ElementsAre(kIsNotJankyV4, kNullJankV4Data,
kIsNotJankyV4, kNullJankV4Data));
}
}
// Frames presented: 31 damaging, 61 total.
for (int damaging_frame = 62; damaging_frame <= 64; damaging_frame++) {
AdvanceByVsyncs(1);
viz::BeginFrameArgs args = CreateNextBeginFrameArgs();
EventMetrics::List metrics;
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_ + kVsyncInterval / 2,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
processor_.ProcessEventsMetricsForPresentedFrame(
metrics, next_presentation_ts_, args);
EXPECT_THAT(metrics, ElementsAre(kIsNotJankyV4, kNullJankV4Data));
}
// Frames presented: 34 damaging, 64 total.
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.FixedWindow", 0);
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.PerScroll", 0);
}
{
base::HistogramTester histogram_tester;
{
AdvanceByVsyncs(1);
viz::BeginFrameArgs args = CreateNextBeginFrameArgs();
EventMetrics::List metrics;
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_ + kVsyncInterval / 2,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
processor_.ProcessEventsMetricsForPresentedFrame(
metrics, next_presentation_ts_, args);
EXPECT_THAT(metrics, ElementsAre(kIsNotJankyV4, kNullJankV4Data));
}
// Frames presented: 35 damaging, 65 total.
switch (GetParam().variant) {
case TestVariant::kLegacyBehavior:
case TestVariant::kNewBehaviorCountDamagingFramesOnly:
// Non-damaging frames don't count towards the histogram frame count, so
// the processor shouldn't emit any histograms yet because it has only
// seen 35 damaging frames so far.
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.FixedWindow", 0);
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.PerScroll", 0);
break;
case TestVariant::kNewBehaviorCountAllFrames:
// Non-damaging frames count towards the histogram frame count, so
// the processor should emit fixed window histograms now because it has
// seen 65 frames in total.
histogram_tester.ExpectUniqueSample(
"Event.ScrollJank.DelayedFramesPercentage4.FixedWindow", 0, 1);
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.PerScroll", 0);
}
}
// Switch to a fling with one input per frame.
{
base::HistogramTester histogram_tester;
// Interleave non-damaging and damaging frames, but this time the
// non-damaging frames are presented.
for (int damaging_frame = 36; damaging_frame <= 64; damaging_frame++) {
AdvanceByVsyncs(1);
viz::BeginFrameArgs non_damaging_args = CreateNextBeginFrameArgs();
EventMetrics::List non_damaging_metrics;
non_damaging_metrics.push_back(
metrics_creator_.CreateInertialGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 2.0f,
.caused_frame_update = false,
.did_scroll = false,
.begin_frame_args = non_damaging_args}));
processor_.ProcessEventsMetricsForPresentedFrame(
non_damaging_metrics, next_presentation_ts_, non_damaging_args);
switch (GetParam().variant) {
case TestVariant::kLegacyBehavior:
EXPECT_THAT(non_damaging_metrics, ElementsAre(kNullJankV4Data));
break;
case TestVariant::kNewBehaviorCountDamagingFramesOnly:
case TestVariant::kNewBehaviorCountAllFrames:
EXPECT_THAT(non_damaging_metrics, ElementsAre(kIsNotJankyV4));
}
AdvanceByVsyncs(1);
viz::BeginFrameArgs damaging_args = CreateNextBeginFrameArgs();
EventMetrics::List damaging_metrics;
damaging_metrics.push_back(
metrics_creator_.CreateInertialGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 2.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = damaging_args}));
processor_.ProcessEventsMetricsForPresentedFrame(
damaging_metrics, next_presentation_ts_, damaging_args);
switch (GetParam().variant) {
case TestVariant::kLegacyBehavior:
EXPECT_THAT(damaging_metrics,
ElementsAre(IsJankyV4WithMissedVsyncCounts(
{{damaging_frame == 36
? JankReason::kMissedVsyncAtStartOfFling
: JankReason::kMissedVsyncDuringFling,
1}})));
break;
case TestVariant::kNewBehaviorCountDamagingFramesOnly:
case TestVariant::kNewBehaviorCountAllFrames:
EXPECT_THAT(damaging_metrics, ElementsAre(kIsNotJankyV4));
}
}
// Frames presented: 64 damaging, 123 total.
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.FixedWindow", 0);
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.PerScroll", 0);
}
{
base::HistogramTester histogram_tester;
AdvanceByVsyncs(1);
viz::BeginFrameArgs args = CreateNextBeginFrameArgs();
EventMetrics::List metrics;
metrics.push_back(metrics_creator_.CreateInertialGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 2.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
processor_.ProcessEventsMetricsForPresentedFrame(
metrics, next_presentation_ts_, args);
EXPECT_THAT(metrics, ElementsAre(kIsNotJankyV4));
// Frames presented: 65 damaging, 124 total.
switch (GetParam().variant) {
case TestVariant::kLegacyBehavior:
// The processor has finally seen 65 damaging frames, so it should emit
// fixed window histograms.
histogram_tester.ExpectUniqueSample(
"Event.ScrollJank.DelayedFramesPercentage4.FixedWindow",
59 * 100 / 64 /* Frames 2-31 & 36-64 */, 1);
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.PerScroll", 0);
break;
case TestVariant::kNewBehaviorCountDamagingFramesOnly:
// The processor has finally seen 65 damaging frames, so it should emit
// fixed window histograms.
histogram_tester.ExpectUniqueSample(
"Event.ScrollJank.DelayedFramesPercentage4.FixedWindow", 0, 1);
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.PerScroll", 0);
break;
case TestVariant::kNewBehaviorCountAllFrames:
// The processor has seen 124 frames in total, which is not at the
// window boundary, so it shouldn't emit any histograms.
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.FixedWindow", 0);
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.PerScroll", 0);
}
}
// Present 4 more damaging frames.
{
base::HistogramTester histogram_tester;
for (int damaging_frame = 66; damaging_frame <= 69; damaging_frame++) {
AdvanceByVsyncs(1);
viz::BeginFrameArgs args = CreateNextBeginFrameArgs();
EventMetrics::List metrics;
metrics.push_back(metrics_creator_.CreateInertialGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 2.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
processor_.ProcessEventsMetricsForPresentedFrame(
metrics, next_presentation_ts_, args);
EXPECT_THAT(metrics, ElementsAre(kIsNotJankyV4));
}
// Frames presented: 69 damaging, 128 total.
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.FixedWindow", 0);
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.PerScroll", 0);
}
// Finally, end the scroll.
{
base::HistogramTester histogram_tester;
AdvanceByVsyncs(1);
viz::BeginFrameArgs end_args = CreateNextBeginFrameArgs();
EventMetrics::List end_metrics;
end_metrics.push_back(metrics_creator_.CreateInertialGestureScrollEnd(
{.timestamp = next_input_generation_ts_,
.caused_frame_update = false,
.begin_frame_args = end_args}));
processor_.ProcessEventsMetricsForPresentedFrame(
end_metrics, next_presentation_ts_, end_args);
switch (GetParam().variant) {
case TestVariant::kLegacyBehavior:
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.FixedWindow", 0);
histogram_tester.ExpectUniqueSample(
"Event.ScrollJank.DelayedFramesPercentage4.PerScroll",
59 * 100 / 69 /* Frames 2-31 & 36-64 */, 1);
break;
case TestVariant::kNewBehaviorCountDamagingFramesOnly:
case TestVariant::kNewBehaviorCountAllFrames:
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.FixedWindow", 0);
histogram_tester.ExpectUniqueSample(
"Event.ScrollJank.DelayedFramesPercentage4.PerScroll", 0, 1);
}
}
}
/*
Tests the behavior of the scroll jank v4 metric on inconsistent input delivery
with both damaging and non-damaging frames.
Both `TestVariant::kNewBehaviorCountDamagingFramesOnly` and
`TestVariant::kNewBehaviorCountAllFrames` should correctly mark frames which
missed one or more VSyncs as janky.
`TestVariant::kLegacyBehavior` yields several false positives because it ignores
non-damaging inputs.
*/
TEST_P(ScrollJankV4ProcessorTest, InconsistentMixedFrameProduction) {
// Start with a regular scroll with two inputs per frame.
{
base::HistogramTester histogram_tester;
{
viz::BeginFrameArgs args = CreateNextBeginFrameArgs();
EventMetrics::List first_metrics;
first_metrics.push_back(metrics_creator_.CreateFirstGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
first_metrics.push_back(metrics_creator_.CreateFirstGestureScrollUpdate(
{.timestamp = next_input_generation_ts_ + kVsyncInterval / 2,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
processor_.ProcessEventsMetricsForPresentedFrame(
first_metrics, next_presentation_ts_, args);
EXPECT_THAT(first_metrics, ElementsAre(kIsNotJankyV4, kNullJankV4Data));
}
// Frames presented: 1 damaging, 1 total.
// Interleave damaging and non-damaging frames.
for (int damaging_frame = 2; damaging_frame <= 10; damaging_frame++) {
AdvanceByVsyncs(1);
viz::BeginFrameArgs non_damaging_args = CreateNextBeginFrameArgs();
AdvanceByVsyncs(1);
viz::BeginFrameArgs damaging_args = CreateNextBeginFrameArgs();
EventMetrics::List metrics;
// Two inputs for a non-damaging frame.
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_ - kVsyncInterval,
.delta = 5.0f,
.caused_frame_update = false,
.did_scroll = false,
.begin_frame_args = non_damaging_args}));
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_ - kVsyncInterval / 2,
.delta = 5.0f,
.caused_frame_update = false,
.did_scroll = false,
.begin_frame_args = non_damaging_args}));
// Two inputs for a presented damaging frame.
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = damaging_args}));
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_ + kVsyncInterval / 2,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = damaging_args}));
processor_.ProcessEventsMetricsForPresentedFrame(
metrics, next_presentation_ts_, damaging_args);
switch (GetParam().variant) {
case TestVariant::kLegacyBehavior:
// The legacy behavior ignores `metrics[0]` (because it's
// non-damaging) and marks `metrics[2]` as janky due to the fast
// scroll rule (because the metric sees a missed VSync between two
// consecutive damaging frames).
EXPECT_THAT(
metrics,
ElementsAre(kNullJankV4Data, kNullJankV4Data,
IsJankyV4WithMissedVsyncCounts(
{{JankReason::kMissedVsyncDuringFastScroll, 1}}),
kNullJankV4Data));
break;
case TestVariant::kNewBehaviorCountDamagingFramesOnly:
case TestVariant::kNewBehaviorCountAllFrames:
// The new behavior identifies one non-damaging frame (starting with
// `metrics[0]`) and one damaging frame (starting with `metrics[2]`).
// It doesn't observe any missed VSyncs, so it doesn't mark any frame
// as janky.
EXPECT_THAT(metrics, ElementsAre(kIsNotJankyV4, kNullJankV4Data,
kIsNotJankyV4, kNullJankV4Data));
}
}
// Frames presented: 10 damaging, 19 total.
// Frame 11 is janky because:
// 1. It violates the running consistency rule because the first input
// should have been presented 1 VSync earlier (based on Chrome's past
// performance).
// 2. It violates the fast scroll continuity rule because there's more
// than 1 VSync between two consecutive presented frames containing
// inputs.
{
AdvanceByVsyncs(3);
viz::BeginFrameArgs non_damaging_args = CreateNextBeginFrameArgs();
AdvanceByVsyncs(1);
viz::BeginFrameArgs damaging_args = CreateNextBeginFrameArgs();
EventMetrics::List metrics;
// Three inputs for a non-damaging frame.
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_ - 1.5 * kVsyncInterval,
.delta = 5.0f,
.caused_frame_update = false,
.did_scroll = false,
.begin_frame_args = non_damaging_args}));
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_ - kVsyncInterval,
.delta = 5.0f,
.caused_frame_update = false,
.did_scroll = false,
.begin_frame_args = non_damaging_args}));
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_ - kVsyncInterval / 2,
.delta = 5.0f,
.caused_frame_update = false,
.did_scroll = false,
.begin_frame_args = non_damaging_args}));
// Two inputs for a presented damaging frame.
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = damaging_args}));
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_ + kVsyncInterval / 2,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = damaging_args}));
processor_.ProcessEventsMetricsForPresentedFrame(
metrics, next_presentation_ts_, damaging_args);
switch (GetParam().variant) {
case TestVariant::kLegacyBehavior:
// The legacy behavior ignores `metrics[0]` (because it's
// non-damaging) and marks `metrics[3]` as janky due to the fast
// scroll rule (because the metric sees 3 missed VSync between two
// consecutive damaging frames). Note that the legacy behavior
// completely misses that there was also a violation of the running
// consistency rule (see the `TestVariant::kNewBehavior.*` case
// below).
EXPECT_THAT(
metrics,
ElementsAre(kNullJankV4Data, kNullJankV4Data, kNullJankV4Data,
IsJankyV4WithMissedVsyncCounts(
{{JankReason::kMissedVsyncDuringFastScroll, 3}}),
kNullJankV4Data));
break;
case TestVariant::kNewBehaviorCountDamagingFramesOnly:
case TestVariant::kNewBehaviorCountAllFrames:
// The new behavior marks the non-damaging frame (starting with
// `metrics[0]`) as janky because:
// 1. `metrics[0]` should have been included in a begin frame one
// VSync earlier.
// 2. There were 2 VSyncs missed (with no inputs) before the
// non-damaging frame during a fast scroll.
EXPECT_THAT(
metrics,
ElementsAre(
IsJankyV4WithMissedVsyncCounts(
{{JankReason::
kMissedVsyncDueToDeceleratingInputFrameDelivery,
1},
{JankReason::kMissedVsyncDuringFastScroll, 2}}),
kNullJankV4Data, kNullJankV4Data, kIsNotJankyV4,
kNullJankV4Data));
}
}
// Frames presented: 11 damaging, 21 total.
for (int damaging_frame = 12; damaging_frame <= 31; damaging_frame++) {
AdvanceByVsyncs(1);
viz::BeginFrameArgs non_damaging_args = CreateNextBeginFrameArgs();
AdvanceByVsyncs(1);
viz::BeginFrameArgs damaging_args = CreateNextBeginFrameArgs();
EventMetrics::List metrics;
// Two inputs for a non-damaging frame.
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_ - kVsyncInterval,
.delta = 5.0f,
.caused_frame_update = false,
.did_scroll = false,
.begin_frame_args = non_damaging_args}));
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_ - kVsyncInterval / 2,
.delta = 5.0f,
.caused_frame_update = false,
.did_scroll = false,
.begin_frame_args = non_damaging_args}));
// Two inputs for a presented damaging frame.
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = damaging_args}));
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_ + kVsyncInterval / 2,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = damaging_args}));
processor_.ProcessEventsMetricsForPresentedFrame(
metrics, next_presentation_ts_, damaging_args);
switch (GetParam().variant) {
case TestVariant::kLegacyBehavior:
// The legacy behavior ignores `metrics[0]` (because it's
// non-damaging) and marks `metrics[2]` as janky due to the fast
// scroll rule (because the metric sees a missed VSync between two
// consecutive damaging frames).
EXPECT_THAT(
metrics,
ElementsAre(kNullJankV4Data, kNullJankV4Data,
IsJankyV4WithMissedVsyncCounts(
{{JankReason::kMissedVsyncDuringFastScroll, 1}}),
kNullJankV4Data));
break;
case TestVariant::kNewBehaviorCountDamagingFramesOnly:
case TestVariant::kNewBehaviorCountAllFrames:
// The new behavior identifies one non-damaging frame (starting with
// `metrics[0]`) and one damaging frame (starting with `metrics[2]`).
// It doesn't observe any missed VSyncs, so it doesn't mark any frame
// as janky.
EXPECT_THAT(metrics, ElementsAre(kIsNotJankyV4, kNullJankV4Data,
kIsNotJankyV4, kNullJankV4Data));
}
}
// Frames presented: 31 damaging, 61 total.
for (int damaging_frame = 32; damaging_frame <= 34; damaging_frame++) {
AdvanceByVsyncs(1);
viz::BeginFrameArgs args = CreateNextBeginFrameArgs();
EventMetrics::List metrics;
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_ + kVsyncInterval / 2,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
processor_.ProcessEventsMetricsForPresentedFrame(
metrics, next_presentation_ts_, args);
EXPECT_THAT(metrics, ElementsAre(kIsNotJankyV4, kNullJankV4Data));
}
// Frames presented: 34 damaging, 64 total.
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.FixedWindow", 0);
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.PerScroll", 0);
}
{
base::HistogramTester histogram_tester;
{
AdvanceByVsyncs(1);
viz::BeginFrameArgs args = CreateNextBeginFrameArgs();
EventMetrics::List metrics;
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
metrics.push_back(metrics_creator_.CreateGestureScrollUpdate(
{.timestamp = next_input_generation_ts_ + kVsyncInterval / 2,
.delta = 5.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
processor_.ProcessEventsMetricsForPresentedFrame(
metrics, next_presentation_ts_, args);
EXPECT_THAT(metrics, ElementsAre(kIsNotJankyV4, kNullJankV4Data));
}
// Frames presented: 35 damaging, 65 total.
switch (GetParam().variant) {
case TestVariant::kLegacyBehavior:
case TestVariant::kNewBehaviorCountDamagingFramesOnly:
// Non-damaging frames don't count towards the histogram frame count, so
// the processor shouldn't emit any histograms yet because it has only
// seen 35 damaging frames so far.
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.FixedWindow", 0);
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.PerScroll", 0);
break;
case TestVariant::kNewBehaviorCountAllFrames:
// Non-damaging frames count towards the histogram frame count, so
// the processor should emit fixed window histograms now because it has
// seen 65 frames in total.
histogram_tester.ExpectUniqueSample(
"Event.ScrollJank.DelayedFramesPercentage4.FixedWindow",
1 * 100 / 64 /* Frame 11 */, 1);
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.PerScroll", 0);
}
}
// Switch to a fling with one input per frame.
{
base::HistogramTester histogram_tester;
// Interleave non-damaging and damaging frames, but this time the
// non-damaging frames are presented.
{
// Frame 36 is janky. It violates the fling continuity rule because
// Chrome missed 5 VSyncs at the transition from a fast regular scroll to
// a fast fling as janky.
AdvanceByVsyncs(6);
viz::BeginFrameArgs non_damaging_args = CreateNextBeginFrameArgs();
EventMetrics::List non_damaging_metrics;
non_damaging_metrics.push_back(
metrics_creator_.CreateInertialGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 2.0f,
.caused_frame_update = false,
.did_scroll = false,
.begin_frame_args = non_damaging_args}));
processor_.ProcessEventsMetricsForPresentedFrame(
non_damaging_metrics, next_presentation_ts_, non_damaging_args);
switch (GetParam().variant) {
case TestVariant::kLegacyBehavior:
EXPECT_THAT(non_damaging_metrics, ElementsAre(kNullJankV4Data));
break;
case TestVariant::kNewBehaviorCountDamagingFramesOnly:
case TestVariant::kNewBehaviorCountAllFrames:
// The new behavior marks the non-damaging frame as janky.
EXPECT_THAT(non_damaging_metrics,
ElementsAre(IsJankyV4WithMissedVsyncCounts(
{{JankReason::kMissedVsyncAtStartOfFling, 5}})));
}
AdvanceByVsyncs(1);
viz::BeginFrameArgs damaging_args = CreateNextBeginFrameArgs();
EventMetrics::List damaging_metrics;
damaging_metrics.push_back(
metrics_creator_.CreateInertialGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 2.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = damaging_args}));
processor_.ProcessEventsMetricsForPresentedFrame(
damaging_metrics, next_presentation_ts_, damaging_args);
switch (GetParam().variant) {
case TestVariant::kLegacyBehavior:
// Whereas the legacy behavior marks the subsequent damaging frame as
// janky (with one more VSync than it should).
EXPECT_THAT(damaging_metrics,
ElementsAre(IsJankyV4WithMissedVsyncCounts(
{{JankReason::kMissedVsyncAtStartOfFling, 6}})));
break;
case TestVariant::kNewBehaviorCountDamagingFramesOnly:
case TestVariant::kNewBehaviorCountAllFrames:
EXPECT_THAT(damaging_metrics, ElementsAre(kIsNotJankyV4));
}
}
// Frames presented: 36 damaging, 67 total.
// Continue interleaving non-damaging and damaging frames.
for (int damaging_frame = 37; damaging_frame <= 64; damaging_frame++) {
AdvanceByVsyncs(1);
viz::BeginFrameArgs non_damaging_args = CreateNextBeginFrameArgs();
EventMetrics::List non_damaging_metrics;
non_damaging_metrics.push_back(
metrics_creator_.CreateInertialGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 2.0f,
.caused_frame_update = false,
.did_scroll = false,
.begin_frame_args = non_damaging_args}));
processor_.ProcessEventsMetricsForPresentedFrame(
non_damaging_metrics, next_presentation_ts_, non_damaging_args);
switch (GetParam().variant) {
case TestVariant::kLegacyBehavior:
EXPECT_THAT(non_damaging_metrics, ElementsAre(kNullJankV4Data));
break;
case TestVariant::kNewBehaviorCountDamagingFramesOnly:
case TestVariant::kNewBehaviorCountAllFrames:
EXPECT_THAT(non_damaging_metrics, ElementsAre(kIsNotJankyV4));
}
AdvanceByVsyncs(1);
viz::BeginFrameArgs damaging_args = CreateNextBeginFrameArgs();
EventMetrics::List damaging_metrics;
damaging_metrics.push_back(
metrics_creator_.CreateInertialGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 2.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = damaging_args}));
processor_.ProcessEventsMetricsForPresentedFrame(
damaging_metrics, next_presentation_ts_, damaging_args);
switch (GetParam().variant) {
case TestVariant::kLegacyBehavior:
EXPECT_THAT(damaging_metrics,
ElementsAre(IsJankyV4WithMissedVsyncCounts(
{{JankReason::kMissedVsyncDuringFling, 1}})));
break;
case TestVariant::kNewBehaviorCountDamagingFramesOnly:
case TestVariant::kNewBehaviorCountAllFrames:
EXPECT_THAT(damaging_metrics, ElementsAre(kIsNotJankyV4));
}
}
// Frames presented: 64 damaging, 123 total.
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.FixedWindow", 0);
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.PerScroll", 0);
}
{
base::HistogramTester histogram_tester;
AdvanceByVsyncs(1);
viz::BeginFrameArgs args = CreateNextBeginFrameArgs();
EventMetrics::List last_metrics_in_fixed_window;
last_metrics_in_fixed_window.push_back(
metrics_creator_.CreateInertialGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 2.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
processor_.ProcessEventsMetricsForPresentedFrame(
last_metrics_in_fixed_window, next_presentation_ts_, args);
EXPECT_THAT(last_metrics_in_fixed_window, ElementsAre(kIsNotJankyV4));
// Frames presented: 65 damaging, 124 total.
switch (GetParam().variant) {
case TestVariant::kLegacyBehavior:
// The processor has finally seen 65 damaging frames, so it should emit
// fixed window histograms.
histogram_tester.ExpectUniqueSample(
"Event.ScrollJank.DelayedFramesPercentage4.FixedWindow",
59 * 100 / 64 /* Frames 2-31 & 36-64 */, 1);
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.PerScroll", 0);
break;
case TestVariant::kNewBehaviorCountDamagingFramesOnly:
// The processor has finally seen 65 damaging frames, so it should emit
// fixed window histograms.
histogram_tester.ExpectUniqueSample(
"Event.ScrollJank.DelayedFramesPercentage4.FixedWindow",
2 * 100 / 64 /* Frames 11 & 36 */, 1);
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.PerScroll", 0);
break;
case TestVariant::kNewBehaviorCountAllFrames:
// The processor has seen 124 frames in total, which is not at the
// window boundary, so it shouldn't emit any histograms.
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.FixedWindow", 0);
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.PerScroll", 0);
}
}
// Frames presented: 65 damaging, 124 total.
{
base::HistogramTester histogram_tester;
// Frame 66 is janky because It violates the fling continuity rule because
// Chrome missed 9 VSyncs in the middle of a fast fling.
{
AdvanceByVsyncs(10);
viz::BeginFrameArgs non_damaging_args = CreateNextBeginFrameArgs();
EventMetrics::List non_damaging_metrics;
non_damaging_metrics.push_back(
metrics_creator_.CreateInertialGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 2.0f,
.caused_frame_update = false,
.did_scroll = false,
.begin_frame_args = non_damaging_args}));
processor_.ProcessEventsMetricsForPresentedFrame(
non_damaging_metrics, next_presentation_ts_, non_damaging_args);
switch (GetParam().variant) {
case TestVariant::kLegacyBehavior:
EXPECT_THAT(non_damaging_metrics, ElementsAre(kNullJankV4Data));
break;
case TestVariant::kNewBehaviorCountDamagingFramesOnly:
case TestVariant::kNewBehaviorCountAllFrames:
// The new behavior marks the non-damaging frame as janky.
EXPECT_THAT(non_damaging_metrics,
ElementsAre(IsJankyV4WithMissedVsyncCounts(
{{JankReason::kMissedVsyncDuringFling, 9}})));
}
AdvanceByVsyncs(1);
viz::BeginFrameArgs damaging_args = CreateNextBeginFrameArgs();
EventMetrics::List damaging_metrics;
damaging_metrics.push_back(
metrics_creator_.CreateInertialGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 2.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = damaging_args}));
processor_.ProcessEventsMetricsForPresentedFrame(
damaging_metrics, next_presentation_ts_, damaging_args);
switch (GetParam().variant) {
case TestVariant::kLegacyBehavior:
// Whereas the legacy behavior marks the subsequent damaging frame as
// janky (with one more VSync than it should).
EXPECT_THAT(damaging_metrics,
ElementsAre(IsJankyV4WithMissedVsyncCounts(
{{JankReason::kMissedVsyncDuringFling, 10}})));
break;
case TestVariant::kNewBehaviorCountDamagingFramesOnly:
case TestVariant::kNewBehaviorCountAllFrames:
EXPECT_THAT(damaging_metrics, ElementsAre(kIsNotJankyV4));
}
}
// Frames presented: 66 damaging, 126 total.
// Present 2 more damaging frames.
for (int damaging_frame = 67; damaging_frame <= 68; damaging_frame++) {
AdvanceByVsyncs(1);
viz::BeginFrameArgs args = CreateNextBeginFrameArgs();
EventMetrics::List metrics;
metrics.push_back(metrics_creator_.CreateInertialGestureScrollUpdate(
{.timestamp = next_input_generation_ts_,
.delta = 2.0f,
.caused_frame_update = true,
.did_scroll = true,
.begin_frame_args = args}));
processor_.ProcessEventsMetricsForPresentedFrame(
metrics, next_presentation_ts_, args);
EXPECT_THAT(metrics, ElementsAre(kIsNotJankyV4));
}
// Frames presented: 68 damaging, 128 total.
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.FixedWindow", 0);
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.PerScroll", 0);
}
// Finally, end the scroll.
{
base::HistogramTester histogram_tester;
AdvanceByVsyncs(1);
viz::BeginFrameArgs end_args = CreateNextBeginFrameArgs();
EventMetrics::List end_metrics;
end_metrics.push_back(metrics_creator_.CreateInertialGestureScrollEnd(
{.timestamp = next_input_generation_ts_,
.caused_frame_update = false,
.begin_frame_args = end_args}));
processor_.ProcessEventsMetricsForPresentedFrame(
end_metrics, next_presentation_ts_, end_args);
switch (GetParam().variant) {
case TestVariant::kLegacyBehavior:
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.FixedWindow", 0);
histogram_tester.ExpectUniqueSample(
"Event.ScrollJank.DelayedFramesPercentage4.PerScroll",
60 * 100 / 68 /* Frames 2-31, 36-64 & 66 */, 1);
break;
case TestVariant::kNewBehaviorCountDamagingFramesOnly:
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.FixedWindow", 0);
histogram_tester.ExpectUniqueSample(
"Event.ScrollJank.DelayedFramesPercentage4.PerScroll",
3 * 100 / 68 /* Frames 11, 36 & 66 */, 1);
break;
case TestVariant::kNewBehaviorCountAllFrames:
histogram_tester.ExpectTotalCount(
"Event.ScrollJank.DelayedFramesPercentage4.FixedWindow", 0);
histogram_tester.ExpectUniqueSample(
"Event.ScrollJank.DelayedFramesPercentage4.PerScroll",
3 * 100 / 128 /* Frames 11, 36 & 66 */, 1);
}
}
}
INSTANTIATE_TEST_SUITE_P(
ScrollJankV4ProcessorTest,
ScrollJankV4ProcessorTest,
testing::ValuesIn<ScrollJankV4ProcessorTestCase>({
{
.variant = TestVariant::kLegacyBehavior,
.test_name = "LegacyBehavior",
},
{
.variant = TestVariant::kNewBehaviorCountDamagingFramesOnly,
.test_name = "NewBehaviorCountDamagingFramesOnly",
},
{
.variant = TestVariant::kNewBehaviorCountAllFrames,
.test_name = "NewBehaviorCountAllFrames",
},
}),
[](const testing::TestParamInfo<ScrollJankV4ProcessorTest::ParamType>&
info) { return info.param.test_name; });
} // namespace cc