blob: 67423fd1b8443b8e1997a68ff113a50dc4bab3ef [file] [log] [blame]
// Copyright 2023 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/predictor_jank_tracker.h"
#include <array>
#include <memory>
#include <string>
#include <vector>
#include "base/strings/string_number_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/test_trace_processor.h"
#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cc {
class PredictorJankTrackerTest : public testing::Test {
public:
PredictorJankTrackerTest() = default;
void SetUp() override {
trace_id_ = 0;
histogram_tester_ = std::make_unique<base::HistogramTester>();
predictor_jank_tracker_ = std::make_unique<PredictorJankTracker>();
base_presentation_ts_ = base::TimeTicks::Min();
}
void MockFrameProduction(double delta, base::TimeTicks presentation_ts) {
predictor_jank_tracker_->ReportLatestScrollDelta(
delta, presentation_ts, vsync_interval,
EventMetrics::TraceId(++trace_id_));
}
int64_t trace_id_ = 0;
std::unique_ptr<base::HistogramTester> histogram_tester_;
std::unique_ptr<PredictorJankTracker> predictor_jank_tracker_;
base::TimeTicks base_presentation_ts_;
::base::test::TracingEnvironment tracing_environment_;
const char* missed_fast_histogram_name =
"Event.Jank.ScrollUpdate.FastScroll.MissedVsync."
"FrameAboveJankyThreshold2";
const char* fast_histogram_name =
"Event.Jank.ScrollUpdate.FastScroll.NoMissedVsync."
"FrameAboveJankyThreshold2";
const char* missed_slow_histogram_name =
"Event.Jank.ScrollUpdate.SlowScroll.MissedVsync."
"FrameAboveJankyThreshold2";
const char* slow_histogram_name =
"Event.Jank.ScrollUpdate.SlowScroll.NoMissedVsync."
"FrameAboveJankyThreshold2";
const char* janky_percentage_name =
"Event.Jank.PredictorJankyFramePercentage2";
constexpr static base::TimeDelta vsync_interval = base::Milliseconds(16);
};
TEST_F(PredictorJankTrackerTest, BasicNonMissedUpperJankCase) {
// 50 / 10 = 5.0, > janky threshold and should be reporteed.
// No dropped frame as frames are separated by 16ms.
MockFrameProduction(10, base_presentation_ts_);
MockFrameProduction(50, base_presentation_ts_ + base::Milliseconds(16));
MockFrameProduction(10, base_presentation_ts_ + base::Milliseconds(32));
histogram_tester_->ExpectTotalCount(fast_histogram_name, 1);
// (50 / 10 - 1.2) * 100
EXPECT_EQ(histogram_tester_->GetTotalSum(fast_histogram_name), 380);
}
TEST_F(PredictorJankTrackerTest, BasicNoMissedSlowUpperJankCase) {
// 5 / 1 = 5.0, > janky threshold and should be reporteed.
// No dropped frame as frames are separated by 16ms.
MockFrameProduction(1, base_presentation_ts_);
MockFrameProduction(5, base_presentation_ts_ + base::Milliseconds(16));
MockFrameProduction(1, base_presentation_ts_ + base::Milliseconds(32));
histogram_tester_->ExpectTotalCount(slow_histogram_name, 1);
// (5 / 14 - 1.4) * 100
EXPECT_EQ(histogram_tester_->GetTotalSum(slow_histogram_name), 360);
}
TEST_F(PredictorJankTrackerTest, BasicMissedSlowUpperJankCase) {
// 5 / 1 = 5.0, > janky threshold and should be reporteed.
// No dropped frame as frames are separated by 16ms.
MockFrameProduction(1, base_presentation_ts_);
MockFrameProduction(5, base_presentation_ts_ + base::Milliseconds(32));
MockFrameProduction(1, base_presentation_ts_ + base::Milliseconds(48));
histogram_tester_->ExpectTotalCount(missed_slow_histogram_name, 1);
// (5 / 1 - 1.4) * 100
EXPECT_EQ(histogram_tester_->GetTotalSum(missed_slow_histogram_name), 360);
}
TEST_F(PredictorJankTrackerTest, BasicMissedUpperJankCase) {
// 50 / 10 = 5.0, > janky threshold and should be reporteed.
// There are dropped frames as the first and second frames are 32 ms apart.
MockFrameProduction(10, base_presentation_ts_);
MockFrameProduction(50, base_presentation_ts_ + base::Milliseconds(32));
MockFrameProduction(10, base_presentation_ts_ + base::Milliseconds(48));
histogram_tester_->ExpectTotalCount(missed_fast_histogram_name, 1);
// (50 / 10 - 1.2) * 100
EXPECT_EQ(histogram_tester_->GetTotalSum(missed_fast_histogram_name), 380);
}
TEST_F(PredictorJankTrackerTest, NegativeNoJankFrame) {
// [-10, -5, -1] is an increasing non janky sequence.
MockFrameProduction(-10, base_presentation_ts_);
MockFrameProduction(-5, base_presentation_ts_ + base::Milliseconds(32));
MockFrameProduction(-1, base_presentation_ts_ + base::Milliseconds(48));
histogram_tester_->ExpectTotalCount(missed_fast_histogram_name, 0);
}
TEST_F(PredictorJankTrackerTest, PositiveNoJankFrame) {
// [10, 5, 1] is a decreasing non janky sequence.
MockFrameProduction(10, base_presentation_ts_);
MockFrameProduction(5, base_presentation_ts_ + base::Milliseconds(32));
MockFrameProduction(1, base_presentation_ts_ + base::Milliseconds(48));
histogram_tester_->ExpectTotalCount(missed_fast_histogram_name, 0);
}
TEST_F(PredictorJankTrackerTest, BasicNonMissedLowerJankCase) {
// 50 / 10 = 5.0, > janky threshold and should be reporteed.
// There are dropped frames as the first and seonc frames are 32 ms apart.
MockFrameProduction(50, base_presentation_ts_);
MockFrameProduction(10, base_presentation_ts_ + base::Milliseconds(16));
MockFrameProduction(50, base_presentation_ts_ + base::Milliseconds(32));
histogram_tester_->ExpectTotalCount(fast_histogram_name, 1);
// (50 / 10 - 1.2) * 100
EXPECT_EQ(histogram_tester_->GetTotalSum(fast_histogram_name), 380);
}
TEST_F(PredictorJankTrackerTest, BasicMissedLowerJankCase) {
// 50 / 10 = 5.0, > janky threshold and should be reporteed.
// There are dropped frames as the first and seonc frames are 32 ms apart.
MockFrameProduction(50, base_presentation_ts_);
MockFrameProduction(10, base_presentation_ts_ + base::Milliseconds(32));
MockFrameProduction(50, base_presentation_ts_ + base::Milliseconds(48));
histogram_tester_->ExpectTotalCount(missed_fast_histogram_name, 1);
// (50 / 10 - 1.2) * 100
EXPECT_EQ(histogram_tester_->GetTotalSum(missed_fast_histogram_name), 380);
}
TEST_F(PredictorJankTrackerTest, BasicNonMissedUpperJankCaseWithTracing) {
base::test::TestTraceProcessor ttp;
ttp.StartTrace("input.scrolling");
// 50 / 10 = 5.0, > janky threshold and should be reporteed.
// No dropped frame as frames are separated by 16ms.
MockFrameProduction(10, base_presentation_ts_);
MockFrameProduction(50, base_presentation_ts_ + base::Milliseconds(16));
MockFrameProduction(11, base_presentation_ts_ + base::Milliseconds(32));
absl::Status status = ttp.StopAndParseTrace();
ASSERT_TRUE(status.ok()) << status.message();
std::string query =
R"(
SELECT
EXTRACT_ARG(arg_set_id,
"scroll_predictor_metrics.prev_event_frame_value.delta_value_pixels"
) AS prev_delta,
EXTRACT_ARG(arg_set_id,
"scroll_predictor_metrics.cur_event_frame_value.delta_value_pixels"
) AS cur_delta,
EXTRACT_ARG(arg_set_id,
"scroll_predictor_metrics.next_event_frame_value.delta_value_pixels"
) AS next_delta
FROM
slice
WHERE
name = "PredictorJankTracker::ReportJankyFrame"
)";
auto result = ttp.RunQuery(query);
ASSERT_TRUE(result.has_value()) << result.error();
EXPECT_THAT(result.value(), ::testing::ElementsAre(
std::vector<std::string>{
"prev_delta", "cur_delta", "next_delta"},
std::vector<std::string>{"10", "50", "11"}));
}
TEST_F(PredictorJankTrackerTest, NoReportingDirectionChange) {
// [50, -100, 50] means the user changed their scrolling direction
// and no predictor performance should be reported.
MockFrameProduction(50, base_presentation_ts_);
MockFrameProduction(-100, base_presentation_ts_ + base::Milliseconds(16));
MockFrameProduction(50, base_presentation_ts_ + base::Milliseconds(32));
histogram_tester_->ExpectTotalCount(missed_fast_histogram_name, 0);
histogram_tester_->ExpectTotalCount(fast_histogram_name, 0);
histogram_tester_->ExpectTotalCount(janky_percentage_name, 0);
}
TEST_F(PredictorJankTrackerTest, JankyFramePercentageEmitted) {
// a sequence of 50 frames, with 10 irregular jumps means 20%
// of the frames were janky, and should be reported since frame count
// is more than 50.
std::array<double, 5> pattern = {50, 50, 50, 50, 10};
for (int i = 1; i <= 64; i++, base_presentation_ts_ += vsync_interval) {
MockFrameProduction(pattern[i % 5], base_presentation_ts_);
}
histogram_tester_->ExpectTotalCount(janky_percentage_name, 1);
EXPECT_EQ(histogram_tester_->GetTotalSum(janky_percentage_name), 18);
}
TEST_F(PredictorJankTrackerTest, JankyFramePercentageNotEmitted) {
// a sequence of 49 frames with 20% janky jumps, but the percentage isn't
// reported because we only report the percentage when more than 50 frames
// exist in the sequence.
std::array<double, 5> pattern = {50, 50, 50, 50, 10};
for (int i = 1; i <= 63; i++, base_presentation_ts_ += vsync_interval) {
MockFrameProduction(pattern[i % 5], base_presentation_ts_);
}
histogram_tester_->ExpectTotalCount(janky_percentage_name, 0);
}
TEST_F(PredictorJankTrackerTest, JankyFramePercentageEmittedTwice) {
// Janky frames percentage should be emitted twice, 20% each
// since we have 100 frames with 20% jank in each scroll.
std::array<double, 5> pattern = {50, 50, 50, 50, 10};
for (int i = 1; i <= 128; i++, base_presentation_ts_ += vsync_interval) {
MockFrameProduction(pattern[i % 5], base_presentation_ts_);
}
histogram_tester_->ExpectTotalCount(janky_percentage_name, 2);
EXPECT_EQ(histogram_tester_->GetTotalSum(janky_percentage_name), 38);
}
TEST_F(PredictorJankTrackerTest, JankyFramePercentageEmittedWhenReset) {
// Janky sequence with 20% janky frames, reporting should happen even if
// the scroll was reset to catch smaller scrolls and residue frames from
// previous scrolls.
std::array<double, 5> pattern = {50, 50, 50, 50, 10};
for (int i = 1; i <= 64; i++, base_presentation_ts_ += vsync_interval) {
MockFrameProduction(pattern[i % 5], base_presentation_ts_);
if (i == 25) {
predictor_jank_tracker_->ResetCurrentScrollReporting();
}
}
histogram_tester_->ExpectTotalCount(janky_percentage_name, 1);
EXPECT_EQ(histogram_tester_->GetTotalSum(janky_percentage_name), 18);
}
// Verify that the stdlib implementation of this metric is consistent with
// PredictorJankTracker.
TEST_F(PredictorJankTrackerTest, VerifySqlThresholds) {
base::test::TestTraceProcessor ttp;
ttp.StartTrace("");
absl::Status status = ttp.StopAndParseTrace();
ASSERT_TRUE(status.ok()) << status.message();
std::string query =
R"(
INCLUDE PERFETTO MODULE chrome.scroll_jank.predictor_error;
SELECT
_get_slow_scroll_delta_threshold()
AS slow_scroll_delta_threshold,
_get_slow_scroll_janky_threshold()
AS slow_scroll_janky_threshold,
_get_fast_scroll_janky_threshold()
AS fast_scroll_janky_threshold;
)";
auto result = ttp.RunQuery(query);
ASSERT_TRUE(result.has_value()) << result.error();
auto thresholds = result.value()[1];
ASSERT_TRUE(thresholds.size() == 3);
double slow_scroll_delta_threshold;
base::StringToDouble(thresholds[0], &slow_scroll_delta_threshold);
EXPECT_EQ(static_cast<float>(slow_scroll_delta_threshold),
PredictorJankTracker::GetSlowScrollDeltaThreshold());
double slow_scroll_janky_threshold;
base::StringToDouble(thresholds[1], &slow_scroll_janky_threshold);
EXPECT_EQ(static_cast<float>(slow_scroll_janky_threshold),
PredictorJankTracker::GetSlowScrollJankyThreshold());
double fast_scroll_janky_threshold;
base::StringToDouble(thresholds[2], &fast_scroll_janky_threshold);
EXPECT_EQ(static_cast<float>(fast_scroll_janky_threshold),
PredictorJankTracker::GetFastScrollJankyThreshold());
}
TEST_F(PredictorJankTrackerTest, VerifySqlPredictorJank) {
base::test::TestTraceProcessor ttp;
ttp.StartTrace("input.scrolling");
// 50 / 10 = 5.0, > janky threshold and should be reported as a fast scroll
// threshold using the frame_janky_upper ratio. 55 / 11 = 5, > janky threshold
// and should be reported as a fast scroll threshold using the
// frame_janky_lower ratio.
MockFrameProduction(10, base_presentation_ts_);
MockFrameProduction(50, base_presentation_ts_ + base::Milliseconds(16));
MockFrameProduction(11, base_presentation_ts_ + base::Milliseconds(32));
MockFrameProduction(55, base_presentation_ts_ + base::Milliseconds(48));
absl::Status status = ttp.StopAndParseTrace();
ASSERT_TRUE(status.ok()) << status.message();
std::string query =
R"(
INCLUDE PERFETTO MODULE chrome.scroll_jank.predictor_error;
WITH predictor_metrics AS (
SELECT
EXTRACT_ARG(arg_set_id,
"scroll_predictor_metrics.prev_event_frame_value.delta_value_pixels"
) AS prev_delta,
EXTRACT_ARG(arg_set_id,
"scroll_predictor_metrics.cur_event_frame_value.delta_value_pixels"
) AS cur_delta,
EXTRACT_ARG(arg_set_id,
"scroll_predictor_metrics.next_event_frame_value.delta_value_pixels"
) AS next_delta,
EXTRACT_ARG(arg_set_id,
"scroll_predictor_metrics.janky_value_pixels"
) AS janky_value_pixels
FROM slice
WHERE name = "PredictorJankTracker::ReportJankyFrame")
SELECT
prev_delta,
cur_delta,
next_delta,
-- The jank as calculated in PredictorJankTracker.
janky_value_pixels,
-- Calculate the jank using the definitions in SQL.
_get_predictor_jank(
ABS(prev_delta),
ABS(cur_delta),
ABS(next_delta),
_get_scroll_jank_threshold(
ABS(prev_delta),
ABS(cur_delta),
ABS(next_delta)
)) AS sql_janky_value_pixels
FROM predictor_metrics
)";
auto result = ttp.RunQuery(query);
ASSERT_TRUE(result.has_value()) << result.error();
EXPECT_THAT(
result.value(),
::testing::ElementsAre(
std::vector<std::string>{"prev_delta", "cur_delta", "next_delta",
"janky_value_pixels",
"sql_janky_value_pixels"},
std::vector<std::string>{"10", "50", "11", "3.34545", "3.34545"},
std::vector<std::string>{"50", "11", "55", "3.34545", "3.34545"}));
}
} // namespace cc