0

Add a new experimental version of scroll jank metrics

This CL adds the following new histograms:

Event.ScrollJank.DelayedFramesPercentage.FixedWindow3
Event.ScrollJank.DelayedFramesPercentage.PerScroll3
Event.ScrollJank.MissedVsyncsPercentage.FixedWindow3
Event.ScrollJank.MissedVsyncsPercentage.PerScroll3
Event.ScrollJank.MissedVsyncsSum.FixedWindow3
Event.ScrollJank.MissedVsyncsSum.PerScroll3

The above new metrics aim to address the following old metrics'
shortcomings and eventually replace them:

Event.ScrollJank.DelayedFramesPercentage.FixedWindow
Event.ScrollJank.DelayedFramesPercentage.PerScroll
Event.ScrollJank.MissedVsyncsPercentage.FixedWindow
Event.ScrollJank.MissedVsyncsPercentage.PerScroll
Event.ScrollJank.MissedVsyncsSum.FixedWindow(2)
Event.ScrollJank.MissedVsyncsSum.PerScroll

The metrics currently only differ in that the new metrics correctly
calculate the earliest scroll update that was coalesced into a presented
scroll update (so they might report slightly higher values than the old
metrics). They will diverge more in the future as we address other
shortcomings in follow-up CLs.

Bug: 402148798
Change-Id: I4d9a0b55278a0b09103edfe24611df327fc4aca0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6357111
Reviewed-by: Kartar Singh <kartarsingh@google.com>
Reviewed-by: Jonathan Ross <jonross@chromium.org>
Auto-Submit: Petr Cermak <petrcermak@chromium.org>
Commit-Queue: Petr Cermak <petrcermak@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1434064}
This commit is contained in:
Petr Cermak
2025-03-18 05:04:30 -07:00
committed by Chromium LUCI CQ
parent 74b35d7c08
commit 7bad5feaff
7 changed files with 583 additions and 32 deletions

@ -1647,9 +1647,11 @@ void CompositorFrameReporter::ReportScrollJankMetrics() const {
// This handles cases when we have multiple scroll events. Events for dropped
// frames are reported by the reporter for next presented frame which could
// lead to having multiple scroll events.
// TODO(crbug.com/402148798): Calculate and use earliest_event instead of
// latest_event.
// TODO(crbug.com/402148798): Deprecate usage of latest_event.
ScrollUpdateEventMetrics* earliest_event = nullptr;
base::TimeTicks earliest_event_generation_ts = base::TimeTicks::Max();
ScrollUpdateEventMetrics* latest_event = nullptr;
base::TimeTicks latest_event_generation_ts = base::TimeTicks::Min();
base::TimeTicks last_coalesced_ts = base::TimeTicks::Min();
for (auto& event : events_metrics_) {
TRACE_EVENT("input", "GestureType", "gesture", event->type());
@ -1659,20 +1661,26 @@ void CompositorFrameReporter::ReportScrollJankMetrics() const {
}
total_predicted_delta += scroll_update->predicted_delta();
base::TimeTicks generation_ts = scroll_update->GetDispatchStageTimestamp(
EventMetrics::DispatchStage::kGenerated);
if (!had_gesture_scrolls) {
earliest_event = scroll_update;
earliest_event_generation_ts = generation_ts;
latest_event = scroll_update;
latest_event_generation_ts = generation_ts;
}
had_gesture_scrolls = true;
if (latest_event->GetDispatchStageTimestamp(
EventMetrics::DispatchStage::kGenerated) <
event->GetDispatchStageTimestamp(
EventMetrics::DispatchStage::kGenerated)) {
if (generation_ts < earliest_event_generation_ts) {
earliest_event = scroll_update;
earliest_event_generation_ts = generation_ts;
} else if (generation_ts > latest_event_generation_ts) {
latest_event = scroll_update;
latest_event_generation_ts = generation_ts;
}
last_coalesced_ts =
std::max(last_coalesced_ts, scroll_update->last_timestamp());
switch (event->type()) {
switch (scroll_update->type()) {
case EventMetrics::EventType::kFirstGestureScrollUpdate:
is_scroll_start = true;
[[fallthrough]];
@ -1719,8 +1727,9 @@ void CompositorFrameReporter::ReportScrollJankMetrics() const {
}
if (global_trackers_.scroll_jank_dropped_frame_tracker) {
global_trackers_.scroll_jank_dropped_frame_tracker
->ReportLatestPresentationData(*latest_event, last_coalesced_ts,
end_timestamp, args_.interval);
->ReportLatestPresentationData(*earliest_event, *latest_event,
last_coalesced_ts, end_timestamp,
args_.interval);
}
if (global_trackers_.scroll_jank_ukm_reporter) {
global_trackers_.scroll_jank_ukm_reporter

@ -2715,6 +2715,90 @@ TEST_F(CompositorFrameReportingControllerTest, JankyScrolledFrameArg) {
std::vector<std::string>{"1"}));
}
/*
Test if the new v3 metric logic identifies scroll jank in a scenario where a
frame is dropped.
vsync v1 v2 v3
| | | | |
input GSU1 GSU2 GSU3
| | |
F1: |---------------|
F2: |---------------x (throttled)
F3: |--------------|
The new v3 metric should identify scroll jank because F2 got dropped even though
there was consistent input for a frame to have been generated.
*/
TEST_F(CompositorFrameReportingControllerTest, JankyThrottledScrolledFrameArg) {
base::test::TestTraceProcessor ttp;
ttp.StartTrace("input");
std::unique_ptr<EventMetrics> metrics_1 = CreateScrollUpdateEventMetrics(
ui::ScrollInputType::kWheel, /*is_inertial=*/false,
ScrollUpdateEventMetrics::ScrollUpdateType::kStarted, std::nullopt);
base::TimeTicks event1_generation_ts = metrics_1->GetDispatchStageTimestamp(
EventMetrics::DispatchStage::kGenerated);
std::unique_ptr<EventMetrics> metrics_2 = CreateScrollUpdateEventMetrics(
ui::ScrollInputType::kWheel, /*is_inertial=*/false,
ScrollUpdateEventMetrics::ScrollUpdateType::kContinued, std::nullopt);
base::TimeTicks event2_generation_ts = metrics_2->GetDispatchStageTimestamp(
EventMetrics::DispatchStage::kGenerated);
std::unique_ptr<EventMetrics> metrics_3 = CreateScrollUpdateEventMetrics(
ui::ScrollInputType::kWheel, /*is_inertial=*/false,
ScrollUpdateEventMetrics::ScrollUpdateType::kContinued, std::nullopt);
base::TimeTicks event3_generation_ts = metrics_3->GetDispatchStageTimestamp(
EventMetrics::DispatchStage::kGenerated);
base::TimeDelta vsync_interval = event2_generation_ts - event1_generation_ts;
args_.interval = vsync_interval;
SimulateBeginImplFrame(); // BF1
reporting_controller_.OnFinishImplFrame(current_id_);
EventMetrics::List metrics_list_1;
metrics_list_1.push_back(std::move(metrics_1));
SimulateSubmitCompositorFrame({{}, std::move(metrics_list_1)});
viz::FrameTimingDetails details_1 = {};
details_1.presentation_feedback.timestamp =
event1_generation_ts + base::Microseconds(200);
reporting_controller_.DidPresentCompositorFrame(*current_token_,
details_1); // PF1
SimulateBeginImplFrame(); // BF2
reporting_controller_.OnFinishImplFrame(current_id_);
AdvanceNowByMs(10);
reporting_controller_.DidNotProduceFrame(current_id_,
FrameSkippedReason::kDrawThrottled);
SimulateBeginImplFrame(); // BF3
reporting_controller_.OnFinishImplFrame(current_id_);
EventMetrics::List metrics_list_3;
metrics_list_3.push_back(std::move(metrics_2));
metrics_list_3.push_back(std::move(metrics_3));
SimulateSubmitCompositorFrame({{}, std::move(metrics_list_3)});
viz::FrameTimingDetails details_3 = {};
details_3.presentation_feedback.timestamp =
event3_generation_ts + base::Microseconds(200);
reporting_controller_.DidPresentCompositorFrame(*current_token_,
details_3); // PF3
absl::Status status = ttp.StopAndParseTrace();
ASSERT_TRUE(status.ok()) << status.message();
constexpr char query[] =
R"(
SELECT COUNT(*) AS cnt
FROM slice
WHERE name = 'MissedFrame v3'
)";
auto result = ttp.RunQuery(query);
ASSERT_TRUE(result.has_value()) << result.error();
EXPECT_THAT(result.value(),
::testing::ElementsAre(std::vector<std::string>{"cnt"},
std::vector<std::string>{"1"}));
}
// A simple test that ensures the vsync_interval is copied onto the
// EventLatency.
TEST_F(CompositorFrameReportingControllerTest, VsyncIntervalArg) {

@ -6,6 +6,7 @@
#include <algorithm>
#include "base/check_op.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
@ -86,6 +87,8 @@ ScrollJankDroppedFrameTracker::ScrollJankDroppedFrameTracker() {
// it to -1 essentially ignores first frame.
fixed_window_.num_presented_frames = -1;
experimental_vsync_fixed_window_.num_past_vsyncs = -1;
fixed_window_v3_.num_presented_frames = -1;
fixed_window_v3_.num_past_vsyncs = -1;
}
ScrollJankDroppedFrameTracker::~ScrollJankDroppedFrameTracker() {
@ -95,6 +98,7 @@ ScrollJankDroppedFrameTracker::~ScrollJankDroppedFrameTracker() {
// scroll.
EmitPerScrollHistogramsAndResetCounters();
EmitPerScrollVsyncHistogramsAndResetCounters();
EmitPerScrollV3HistogramsAndResetCounters();
}
}
@ -140,6 +144,30 @@ void ScrollJankDroppedFrameTracker::EmitPerScrollHistogramsAndResetCounters() {
per_scroll_->max_missed_vsyncs = 0;
}
void ScrollJankDroppedFrameTracker::
EmitPerScrollV3HistogramsAndResetCounters() {
// There should be at least one presented frame given the method is only
// called after we have a successful presentation.
if (per_scroll_v3_->num_presented_frames == 0) {
return;
}
UMA_HISTOGRAM_PERCENTAGE(kDelayedFramesPerScrollV3Histogram,
(100 * per_scroll_v3_->missed_frames) /
per_scroll_v3_->num_presented_frames);
UMA_HISTOGRAM_CUSTOM_COUNTS(kMissedVsyncsSumPerScrollV3Histogram,
per_scroll_v3_->missed_vsyncs, kVsyncCountsMin,
kVsyncCountsMax, kVsyncCountsBuckets);
UMA_HISTOGRAM_PERCENTAGE(
kMissedVsyncsPerScrollV3Histogram,
(100 * per_scroll_v3_->missed_vsyncs) / per_scroll_v3_->num_past_vsyncs);
per_scroll_v3_->missed_frames = 0;
per_scroll_v3_->missed_vsyncs = 0;
per_scroll_v3_->num_presented_frames = 0;
per_scroll_v3_->num_past_vsyncs = 0;
}
void ScrollJankDroppedFrameTracker::
EmitPerScrollVsyncHistogramsAndResetCounters() {
// There should be at least one presented frame given the method is only
@ -187,6 +215,28 @@ void ScrollJankDroppedFrameTracker::EmitPerWindowHistogramsAndResetCounters() {
fixed_window_.num_presented_frames = 0;
}
void ScrollJankDroppedFrameTracker::
EmitPerWindowV3HistogramsAndResetCounters() {
DCHECK_EQ(fixed_window_v3_.num_presented_frames, kHistogramEmitFrequency);
UMA_HISTOGRAM_PERCENTAGE(
kDelayedFramesWindowV3Histogram,
(100 * fixed_window_v3_.missed_frames) / kHistogramEmitFrequency);
UMA_HISTOGRAM_CUSTOM_COUNTS(kMissedVsyncsSumInWindowV3Histogram,
fixed_window_v3_.missed_vsyncs, kVsyncCountsMin,
kVsyncCountsMax, kVsyncCountsBuckets);
UMA_HISTOGRAM_PERCENTAGE(kMissedVsyncsWindowV3Histogram,
(100 * fixed_window_v3_.missed_vsyncs) /
fixed_window_v3_.num_past_vsyncs);
fixed_window_v3_.missed_frames = 0;
fixed_window_v3_.missed_vsyncs = 0;
// We don't need to reset these to -1 because after the first window we always
// have a valid previous frame data to compare the first frame of window.
fixed_window_v3_.num_presented_frames = 0;
fixed_window_v3_.num_past_vsyncs = 0;
}
// TODO(b/306611560): Cleanup experimental per vsync metric or promote to
// default.
void ScrollJankDroppedFrameTracker::
@ -212,6 +262,7 @@ void ScrollJankDroppedFrameTracker::
}
void ScrollJankDroppedFrameTracker::ReportLatestPresentationData(
ScrollUpdateEventMetrics& earliest_event,
ScrollUpdateEventMetrics& latest_event,
base::TimeTicks last_input_generation_ts,
base::TimeTicks presentation_ts,
@ -219,6 +270,10 @@ void ScrollJankDroppedFrameTracker::ReportLatestPresentationData(
base::TimeTicks first_input_generation_ts =
latest_event.GetDispatchStageTimestamp(
EventMetrics::DispatchStage::kGenerated);
base::TimeTicks first_input_generation_v3_ts =
earliest_event.GetDispatchStageTimestamp(
EventMetrics::DispatchStage::kGenerated);
CHECK_LE(first_input_generation_v3_ts, first_input_generation_ts);
if ((last_input_generation_ts < first_input_generation_ts) ||
(presentation_ts <= last_input_generation_ts)) {
// TODO(crbug.com/40913586): Investigate when these edge cases can be
@ -339,17 +394,81 @@ void ScrollJankDroppedFrameTracker::ReportLatestPresentationData(
}
DCHECK_LT(fixed_window_.num_presented_frames, kHistogramEmitFrequency);
ReportLatestPresentationDataV3(first_input_generation_v3_ts, presentation_ts,
vsync_interval);
prev_presentation_ts_ = presentation_ts;
prev_last_input_generation_ts_ = last_input_generation_ts;
}
void ScrollJankDroppedFrameTracker::ReportLatestPresentationDataV3(
base::TimeTicks first_input_generation_v3_ts,
base::TimeTicks presentation_ts,
base::TimeDelta vsync_interval) {
if (!per_scroll_v3_.has_value()) {
per_scroll_v3_ = JankDataV3();
}
// The presentation delta is usually 16.6ms for 60 Hz devices,
// but sometimes random errors result in a delta of up to 20ms
// as observed in traces. This adds an error margin of 1/2 a
// vsync before considering the Vsync missed.
bool missed_frame = (presentation_ts - prev_presentation_ts_) >
(vsync_interval + vsync_interval / 2);
bool input_available =
(first_input_generation_v3_ts - prev_last_input_generation_ts_) <
(vsync_interval + vsync_interval / 2);
// Sometimes the vsync interval is not accurate and is slightly more
// than the actual signal arrival time, adding (vsync_interval / 2)
// here insures the result is always ceiled.
int curr_frame_total_vsyncs =
(presentation_ts - prev_presentation_ts_ + (vsync_interval / 2)) /
vsync_interval;
int curr_frame_missed_vsyncs = curr_frame_total_vsyncs - 1;
if (missed_frame && input_available) {
++per_scroll_v3_->missed_frames;
++fixed_window_v3_.missed_frames;
fixed_window_v3_.missed_vsyncs += curr_frame_missed_vsyncs;
per_scroll_v3_->missed_vsyncs += curr_frame_missed_vsyncs;
TRACE_EVENT_INSTANT(
"input,input.scrolling", "MissedFrame v3",
"per_scroll_v3_->missed_frames", per_scroll_v3_->missed_frames,
"per_scroll_v3_->missed_vsyncs", per_scroll_v3_->missed_vsyncs,
"vsync_interval", vsync_interval);
}
++fixed_window_v3_.num_presented_frames;
++per_scroll_v3_->num_presented_frames;
if (fixed_window_v3_.num_past_vsyncs < 0) {
// Ignore the VSyncs in the first frame since it's always deemed non-janky.
fixed_window_v3_.num_past_vsyncs = 0;
// Ignore curr_frame_total_vsyncs in the first frame since it can be
// arbitrarily large due to prev_presentation_ts_ being effectively
// uninitialized.
per_scroll_v3_->num_past_vsyncs = 1;
} else {
fixed_window_v3_.num_past_vsyncs += curr_frame_total_vsyncs;
per_scroll_v3_->num_past_vsyncs += curr_frame_total_vsyncs;
}
if (fixed_window_v3_.num_presented_frames == kHistogramEmitFrequency) {
EmitPerWindowV3HistogramsAndResetCounters();
}
DCHECK_LT(fixed_window_v3_.num_presented_frames, kHistogramEmitFrequency);
}
void ScrollJankDroppedFrameTracker::OnScrollStarted() {
if (per_scroll_.has_value()) {
EmitPerScrollHistogramsAndResetCounters();
EmitPerScrollVsyncHistogramsAndResetCounters();
EmitPerScrollV3HistogramsAndResetCounters();
} else {
per_scroll_ = JankData();
experimental_per_scroll_vsync_ = JankData();
per_scroll_v3_ = JankDataV3();
}
}

@ -23,6 +23,7 @@ class CC_EXPORT ScrollJankDroppedFrameTracker {
ScrollJankDroppedFrameTracker(const ScrollJankDroppedFrameTracker&) = delete;
void ReportLatestPresentationData(ScrollUpdateEventMetrics& earliest_event,
ScrollUpdateEventMetrics& latest_event,
base::TimeTicks last_input_generation_ts,
base::TimeTicks presentation_ts,
base::TimeDelta vsync_interval);
@ -36,16 +37,26 @@ class CC_EXPORT ScrollJankDroppedFrameTracker {
static constexpr int kHistogramEmitFrequency = 64;
static constexpr const char* kDelayedFramesWindowHistogram =
"Event.ScrollJank.DelayedFramesPercentage.FixedWindow";
static constexpr const char* kDelayedFramesWindowV3Histogram =
"Event.ScrollJank.DelayedFramesPercentage.FixedWindow3";
static constexpr const char* kMissedVsyncsWindowHistogram =
"Event.ScrollJank.MissedVsyncsPercentage.FixedWindow";
static constexpr const char* kMissedVsyncsWindowV3Histogram =
"Event.ScrollJank.MissedVsyncsPercentage.FixedWindow3";
static constexpr const char* kDelayedFramesPerScrollHistogram =
"Event.ScrollJank.DelayedFramesPercentage.PerScroll";
static constexpr const char* kDelayedFramesPerScrollV3Histogram =
"Event.ScrollJank.DelayedFramesPercentage.PerScroll3";
static constexpr const char* kMissedVsyncsPerScrollHistogram =
"Event.ScrollJank.MissedVsyncsPercentage.PerScroll";
static constexpr const char* kMissedVsyncsPerScrollV3Histogram =
"Event.ScrollJank.MissedVsyncsPercentage.PerScroll3";
static constexpr const char* kMissedVsyncsSumInWindowHistogram =
"Event.ScrollJank.MissedVsyncsSum.FixedWindow";
static constexpr const char* kMissedVsyncsSumInVsyncWindowHistogram =
"Event.ScrollJank.MissedVsyncsSum.FixedWindow2";
static constexpr const char* kMissedVsyncsSumInWindowV3Histogram =
"Event.ScrollJank.MissedVsyncsSum.FixedWindow3";
static constexpr const char* kMissedVsyncsMaxInWindowHistogram =
"Event.ScrollJank.MissedVsyncsMax.FixedWindow";
static constexpr const char* kMissedVsyncsMaxInVsyncWindowHistogram =
@ -54,6 +65,8 @@ class CC_EXPORT ScrollJankDroppedFrameTracker {
"Event.ScrollJank.MissedVsyncsMax.PerScroll";
static constexpr const char* kMissedVsyncsSumPerScrollHistogram =
"Event.ScrollJank.MissedVsyncsSum.PerScroll";
static constexpr const char* kMissedVsyncsSumPerScrollV3Histogram =
"Event.ScrollJank.MissedVsyncsSum.PerScroll3";
static constexpr const char* kMissedVsyncsPerFrameHistogram =
"Event.ScrollJank.MissedVsyncs.PerFrame";
@ -62,6 +75,12 @@ class CC_EXPORT ScrollJankDroppedFrameTracker {
void EmitPerScrollHistogramsAndResetCounters();
void EmitPerVsyncWindowHistogramsAndResetCounters();
void EmitPerScrollVsyncHistogramsAndResetCounters();
void EmitPerWindowV3HistogramsAndResetCounters();
void EmitPerScrollV3HistogramsAndResetCounters();
void ReportLatestPresentationDataV3(
base::TimeTicks first_input_generation_v3_ts,
base::TimeTicks presentation_ts,
base::TimeDelta vsync_interval);
// We could have two different frames with same presentation time and due to
// this just having previous frame's data is not enough for calculating the
@ -81,12 +100,25 @@ class CC_EXPORT ScrollJankDroppedFrameTracker {
int num_past_vsyncs = 0;
};
struct JankDataV3 {
// Number of frames which were deemed janky.
int missed_frames = 0;
// Number of vsyncs the frames were delayed by. Whenever a frame is missed
// it could be delayed >=1 vsyncs, this helps us track how "long" the janks
// are.
int missed_vsyncs = 0;
int num_presented_frames = 0;
int num_past_vsyncs = 0;
};
JankData fixed_window_;
// TODO(b/306611560): Cleanup experimental per vsync metric or promote to
// default.
JankData experimental_vsync_fixed_window_;
JankDataV3 fixed_window_v3_;
std::optional<JankData> per_scroll_;
std::optional<JankData> experimental_per_scroll_vsync_;
std::optional<JankDataV3> per_scroll_v3_;
raw_ptr<ScrollJankUkmReporter> scroll_jank_ukm_reporter_ = nullptr;
};

@ -5,6 +5,7 @@
#include "cc/metrics/scroll_jank_dropped_frame_tracker.h"
#include <memory>
#include <optional>
#include <string>
#include <vector>
@ -23,18 +24,31 @@ struct FrameTimestamps {
base::TimeTicks first_input_ts;
base::TimeTicks last_input_ts;
base::TimeTicks presentation_ts;
std::optional<base::TimeTicks> earliest_coalesced_input_ts;
FrameTimestamps(base::TimeTicks first_input,
base::TimeTicks last_input,
base::TimeTicks presentation)
FrameTimestamps(
base::TimeTicks first_input,
base::TimeTicks last_input,
base::TimeTicks presentation,
std::optional<base::TimeTicks> earliest_coalesced_input = std::nullopt)
: first_input_ts(first_input),
last_input_ts(last_input),
presentation_ts(presentation) {}
presentation_ts(presentation),
earliest_coalesced_input_ts(earliest_coalesced_input) {}
FrameTimestamps(int first_input, int last_input, int presentation)
FrameTimestamps(int first_input,
int last_input,
int presentation,
std::optional<int> earliest_coalesced_input = std::nullopt)
: first_input_ts(base::TimeTicks() + base::Milliseconds(first_input)),
last_input_ts(base::TimeTicks() + base::Milliseconds(last_input)),
presentation_ts(base::TimeTicks() + base::Milliseconds(presentation)) {}
presentation_ts(base::TimeTicks() + base::Milliseconds(presentation)),
earliest_coalesced_input_ts(
earliest_coalesced_input.has_value()
? std::optional(
base::TimeTicks() +
base::Milliseconds(earliest_coalesced_input.value()))
: std::nullopt) {}
};
constexpr int kHistogramEmitFrequency =
@ -42,14 +56,24 @@ constexpr int kHistogramEmitFrequency =
constexpr int kFirstWindowSize = kHistogramEmitFrequency + 1;
constexpr const char* kDelayedFramesWindowHistogram =
ScrollJankDroppedFrameTracker::kDelayedFramesWindowHistogram;
constexpr const char* kDelayedFramesWindowV3Histogram =
ScrollJankDroppedFrameTracker::kDelayedFramesWindowV3Histogram;
constexpr const char* kMissedVsyncsWindowHistogram =
ScrollJankDroppedFrameTracker::kMissedVsyncsWindowHistogram;
constexpr const char* kMissedVsyncsWindowV3Histogram =
ScrollJankDroppedFrameTracker::kMissedVsyncsWindowV3Histogram;
constexpr const char* kDelayedFramesPerScrollHistogram =
ScrollJankDroppedFrameTracker::kDelayedFramesPerScrollHistogram;
constexpr const char* kDelayedFramesPerScrollV3Histogram =
ScrollJankDroppedFrameTracker::kDelayedFramesPerScrollV3Histogram;
constexpr const char* kMissedVsyncsPerScrollHistogram =
ScrollJankDroppedFrameTracker::kMissedVsyncsPerScrollHistogram;
constexpr const char* kMissedVsyncsPerScrollV3Histogram =
ScrollJankDroppedFrameTracker::kMissedVsyncsPerScrollV3Histogram;
constexpr const char* kMissedVsyncsSumInWindowHistogram =
ScrollJankDroppedFrameTracker::kMissedVsyncsSumInWindowHistogram;
constexpr const char* kMissedVsyncsSumInWindowV3Histogram =
ScrollJankDroppedFrameTracker::kMissedVsyncsSumInWindowV3Histogram;
constexpr const char* kMissedVsyncsSumInVsyncWindowHistogram =
ScrollJankDroppedFrameTracker::kMissedVsyncsSumInVsyncWindowHistogram;
constexpr const char* kMissedVsyncsMaxInWindowHistogram =
@ -58,6 +82,8 @@ constexpr const char* kMissedVsyncsMaxInVsyncWindowHistogram =
ScrollJankDroppedFrameTracker::kMissedVsyncsMaxInVsyncWindowHistogram;
constexpr const char* kMissedVsyncsSumPerScrollHistogram =
ScrollJankDroppedFrameTracker::kMissedVsyncsSumPerScrollHistogram;
constexpr const char* kMissedVsyncsSumPerScrollV3Histogram =
ScrollJankDroppedFrameTracker::kMissedVsyncsSumPerScrollV3Histogram;
constexpr const char* kMissedVsyncsMaxPerScrollHistogram =
ScrollJankDroppedFrameTracker::kMissedVsyncsMaxPerScrollHistogram;
constexpr const char* kMissedVsyncsPerFrameHistogram =
@ -81,20 +107,11 @@ class ScrollJankDroppedFrameTrackerTest : public testing::Test {
prev_frame.first_input_ts += kVsyncInterval;
prev_frame.last_input_ts += kVsyncInterval;
prev_frame.presentation_ts += kVsyncInterval;
base::SimpleTestTickClock tick_clock;
tick_clock.SetNowTicks(prev_frame.first_input_ts);
auto event = ScrollUpdateEventMetrics::CreateForTesting(
ui::EventType::kGestureScrollUpdate, ui::ScrollInputType::kWheel,
/*is_inertial=*/false,
ScrollUpdateEventMetrics::ScrollUpdateType::kContinued,
/*delta=*/10.0f, prev_frame.first_input_ts, base::TimeTicks(),
&tick_clock, /*trace_id=*/std::nullopt);
scroll_jank_dropped_frame_tracker_->ReportLatestPresentationData(
*event.get(), prev_frame.last_input_ts, prev_frame.presentation_ts,
kVsyncInterval);
if (prev_frame.earliest_coalesced_input_ts) {
prev_frame.earliest_coalesced_input_ts =
prev_frame.earliest_coalesced_input_ts.value() + kVsyncInterval;
}
ReportLatestPresentationDataToTracker(prev_frame);
}
return prev_frame;
}
@ -108,9 +125,19 @@ class ScrollJankDroppedFrameTrackerTest : public testing::Test {
ScrollUpdateEventMetrics::ScrollUpdateType::kContinued,
/*delta=*/10.0f, frame.first_input_ts, base::TimeTicks(), &tick_clock,
/*trace_id=*/std::nullopt);
std::unique_ptr<ScrollUpdateEventMetrics> earliest_coalesced_event;
if (frame.earliest_coalesced_input_ts) {
earliest_coalesced_event = ScrollUpdateEventMetrics::CreateForTesting(
ui::EventType::kGestureScrollUpdate, ui::ScrollInputType::kWheel,
/*is_inertial=*/false,
ScrollUpdateEventMetrics::ScrollUpdateType::kContinued,
/*delta=*/10.0f, frame.earliest_coalesced_input_ts.value(),
base::TimeTicks(), &tick_clock,
/*trace_id=*/std::nullopt);
}
scroll_jank_dropped_frame_tracker_->ReportLatestPresentationData(
*event.get(), frame.last_input_ts, frame.presentation_ts,
kVsyncInterval);
earliest_coalesced_event ? *earliest_coalesced_event : *event, *event,
frame.last_input_ts, frame.presentation_ts, kVsyncInterval);
}
std::unique_ptr<base::HistogramTester> histogram_tester;
@ -127,8 +154,12 @@ TEST_F(ScrollJankDroppedFrameTrackerTest, EmitsHistograms) {
ProduceAndReportMockFrames(f1, kHistogramEmitFrequency);
histogram_tester->ExpectUniqueSample(kDelayedFramesWindowHistogram, 0, 0);
histogram_tester->ExpectUniqueSample(kDelayedFramesWindowV3Histogram, 0, 0);
histogram_tester->ExpectUniqueSample(kMissedVsyncsSumInWindowHistogram, 0, 0);
histogram_tester->ExpectUniqueSample(kMissedVsyncsSumInWindowV3Histogram, 0,
0);
histogram_tester->ExpectUniqueSample(kMissedVsyncsWindowHistogram, 0, 0);
histogram_tester->ExpectUniqueSample(kMissedVsyncsWindowV3Histogram, 0, 0);
histogram_tester->ExpectUniqueSample(kMissedVsyncsSumInVsyncWindowHistogram,
0, 0);
@ -136,8 +167,12 @@ TEST_F(ScrollJankDroppedFrameTrackerTest, EmitsHistograms) {
last_frame = ProduceAndReportMockFrames(last_frame, 1);
histogram_tester->ExpectUniqueSample(kDelayedFramesWindowHistogram, 0, 1);
histogram_tester->ExpectUniqueSample(kDelayedFramesWindowV3Histogram, 0, 1);
histogram_tester->ExpectUniqueSample(kMissedVsyncsSumInWindowHistogram, 0, 1);
histogram_tester->ExpectUniqueSample(kMissedVsyncsSumInWindowV3Histogram, 0,
1);
histogram_tester->ExpectUniqueSample(kMissedVsyncsWindowHistogram, 0, 1);
histogram_tester->ExpectUniqueSample(kMissedVsyncsWindowV3Histogram, 0, 1);
histogram_tester->ExpectUniqueSample(kMissedVsyncsSumInVsyncWindowHistogram,
0, 1);
@ -145,8 +180,12 @@ TEST_F(ScrollJankDroppedFrameTrackerTest, EmitsHistograms) {
ProduceAndReportMockFrames(last_frame, kHistogramEmitFrequency);
histogram_tester->ExpectUniqueSample(kDelayedFramesWindowHistogram, 0, 2);
histogram_tester->ExpectUniqueSample(kDelayedFramesWindowV3Histogram, 0, 2);
histogram_tester->ExpectUniqueSample(kMissedVsyncsSumInWindowHistogram, 0, 2);
histogram_tester->ExpectUniqueSample(kMissedVsyncsSumInWindowV3Histogram, 0,
2);
histogram_tester->ExpectUniqueSample(kMissedVsyncsWindowHistogram, 0, 2);
histogram_tester->ExpectUniqueSample(kMissedVsyncsWindowV3Histogram, 0, 2);
histogram_tester->ExpectUniqueSample(kMissedVsyncsSumInVsyncWindowHistogram,
0, 2);
}
@ -175,8 +214,12 @@ TEST_F(ScrollJankDroppedFrameTrackerTest, FrameProducedEveryVsync) {
ProduceAndReportMockFrames(f2, frames_to_emit_histogram);
histogram_tester->ExpectUniqueSample(kDelayedFramesWindowHistogram, 0, 1);
histogram_tester->ExpectUniqueSample(kDelayedFramesWindowV3Histogram, 0, 1);
histogram_tester->ExpectUniqueSample(kMissedVsyncsSumInWindowHistogram, 0, 1);
histogram_tester->ExpectUniqueSample(kMissedVsyncsSumInWindowV3Histogram, 0,
1);
histogram_tester->ExpectUniqueSample(kMissedVsyncsWindowHistogram, 0, 1);
histogram_tester->ExpectUniqueSample(kMissedVsyncsWindowV3Histogram, 0, 1);
histogram_tester->ExpectUniqueSample(kMissedVsyncsSumInVsyncWindowHistogram,
0, 1);
}
@ -206,8 +249,12 @@ TEST_F(ScrollJankDroppedFrameTrackerTest, NoFrameProducedForMissingInput) {
ProduceAndReportMockFrames(f2, frames_to_emit_histogram);
histogram_tester->ExpectUniqueSample(kDelayedFramesWindowHistogram, 0, 1);
histogram_tester->ExpectUniqueSample(kDelayedFramesWindowV3Histogram, 0, 1);
histogram_tester->ExpectUniqueSample(kMissedVsyncsSumInWindowHistogram, 0, 1);
histogram_tester->ExpectUniqueSample(kMissedVsyncsSumInWindowV3Histogram, 0,
1);
histogram_tester->ExpectUniqueSample(kMissedVsyncsWindowHistogram, 0, 1);
histogram_tester->ExpectUniqueSample(kMissedVsyncsWindowV3Histogram, 0, 1);
histogram_tester->ExpectUniqueSample(kMissedVsyncsSumInVsyncWindowHistogram,
0, 1);
}
@ -250,23 +297,93 @@ TEST_F(ScrollJankDroppedFrameTrackerTest, MissedVsyncWhenInputWasPresent) {
// Frame F2 missed 2 vsyncs, F3 missed 1 vsync.
const int expected_max = 2;
const int expected_sum = 3;
const int expected_missed_vsyncs_percentage =
(100 * expected_sum) / kHistogramEmitFrequency;
histogram_tester->ExpectUniqueSample(kDelayedFramesWindowHistogram,
expected_delayed_frames_percentage, 1);
histogram_tester->ExpectUniqueSample(kDelayedFramesWindowV3Histogram,
expected_delayed_frames_percentage, 1);
histogram_tester->ExpectUniqueSample(kMissedVsyncsSumInWindowHistogram,
expected_sum, 1);
histogram_tester->ExpectUniqueSample(kMissedVsyncsSumInWindowV3Histogram,
expected_sum, 1);
histogram_tester->ExpectUniqueSample(kMissedVsyncsMaxInWindowHistogram,
expected_max, 1);
histogram_tester->ExpectUniqueSample(kMissedVsyncsWindowV3Histogram,
expected_missed_vsyncs_percentage, 1);
// The counters were reset for next set of `kHistogramEmitFrequency` frames.
ProduceAndReportMockFrames(last_frame_ts, kHistogramEmitFrequency);
histogram_tester->ExpectBucketCount(kDelayedFramesWindowHistogram, 0, 1);
histogram_tester->ExpectBucketCount(kDelayedFramesWindowV3Histogram, 0, 1);
histogram_tester->ExpectBucketCount(kMissedVsyncsSumInWindowHistogram, 0, 1);
histogram_tester->ExpectBucketCount(kMissedVsyncsSumInWindowV3Histogram, 0,
1);
histogram_tester->ExpectBucketCount(kMissedVsyncsMaxInWindowHistogram, 0, 1);
histogram_tester->ExpectBucketCount(kMissedVsyncsWindowV3Histogram, 0, 1);
// Other non-zero buckets for histogram were tested earlier in the code.
histogram_tester->ExpectBucketCount(kMissedVsyncsPerFrameHistogram, 0, 127);
}
/*
Test that when a coalesced frame took too long to be produced shows up in the
new v3 metric (but not in the old metric).
vsync v0 v1 v2
| | | | | |
input I0 I1 I2 I3 I4 I5 I6
| | | | | | | |
F1: |-----------| {I0, I1}
F2: |-----------------------| {I2(coalesced), I3, I4}
F3: |-----------------------| {I5, I6}
Since the old metric doesn't take coalesced events into account, it ignores I2
and considers the following instead:
F2': |-----------------| {I3, I4}
*/
TEST_F(ScrollJankDroppedFrameTrackerTest,
MissedVsyncWhenCoalescedInputWasPresent) {
const std::vector<int> inputs = {103, 111, 119, 135, 143, 151, 159};
const std::vector<int> presentations = {135, 183, 215};
FrameTimestamps f1 = {inputs[0], inputs[1], presentations[0]};
FrameTimestamps f2 = {inputs[3], inputs[4], presentations[1], inputs[2]};
FrameTimestamps f3 = {inputs[5], inputs[6], presentations[2]};
ReportLatestPresentationDataToTracker(f1);
ReportLatestPresentationDataToTracker(f2);
ReportLatestPresentationDataToTracker(f3);
// To trigger per window histogram emission.
int frames_to_emit_histogram = kFirstWindowSize - 3;
FrameTimestamps last_frame_ts =
ProduceAndReportMockFrames(f3, frames_to_emit_histogram);
// F2 and F3 are janky frames, but only the new v3 metric considers F2 janky
// because it takes coalesced events into account.
histogram_tester->ExpectUniqueSample(kDelayedFramesWindowHistogram,
(1 * 100) / kHistogramEmitFrequency, 1);
histogram_tester->ExpectUniqueSample(kDelayedFramesWindowV3Histogram,
(2 * 100) / kHistogramEmitFrequency, 1);
histogram_tester->ExpectUniqueSample(kMissedVsyncsSumInWindowHistogram, 1, 1);
histogram_tester->ExpectUniqueSample(kMissedVsyncsSumInWindowV3Histogram, 3,
1);
histogram_tester->ExpectUniqueSample(kMissedVsyncsWindowV3Histogram,
(3 * 100) / kHistogramEmitFrequency, 1);
// The counters were reset for next set of `kHistogramEmitFrequency` frames.
ProduceAndReportMockFrames(last_frame_ts, kHistogramEmitFrequency);
histogram_tester->ExpectBucketCount(kDelayedFramesWindowHistogram, 0, 1);
histogram_tester->ExpectBucketCount(kDelayedFramesWindowV3Histogram, 0, 1);
histogram_tester->ExpectBucketCount(kMissedVsyncsSumInWindowHistogram, 0, 1);
histogram_tester->ExpectBucketCount(kMissedVsyncsSumInWindowV3Histogram, 0,
1);
histogram_tester->ExpectBucketCount(kMissedVsyncsWindowV3Histogram, 0, 1);
}
TEST_F(ScrollJankDroppedFrameTrackerTest, MissedVsyncsPerVsyncWindow) {
const std::vector<int> inputs = {103, 111, 119, 127, 135, 143};
const std::vector<int> vsyncs = {148, 196, 228};
@ -356,14 +473,23 @@ TEST_P(PerScrollTests, MetricsEmittedPerScroll) {
// Frame F2 missed 2 vsyncs, F3 missed 1 vsync.
const int expected_max = 2;
const int expected_sum = 3;
const int total_vsyncs = total_frames + expected_sum;
const int expected_missed_vsyncs_percentage =
(100 * expected_sum) / total_vsyncs;
// Emits non-bucketed histograms.
histogram_tester->ExpectUniqueSample(kMissedVsyncsSumPerScrollHistogram,
expected_sum, 1);
histogram_tester->ExpectUniqueSample(kMissedVsyncsSumPerScrollV3Histogram,
expected_sum, 1);
histogram_tester->ExpectUniqueSample(kMissedVsyncsMaxPerScrollHistogram,
expected_max, 1);
histogram_tester->ExpectUniqueSample(kMissedVsyncsPerScrollV3Histogram,
expected_missed_vsyncs_percentage, 1);
histogram_tester->ExpectUniqueSample(kDelayedFramesPerScrollHistogram,
expected_delayed_frames_percentage, 1);
histogram_tester->ExpectUniqueSample(kDelayedFramesPerScrollV3Histogram,
expected_delayed_frames_percentage, 1);
// Emits bucketed histograms.
histogram_tester->ExpectUniqueSample(
@ -383,8 +509,12 @@ TEST_P(PerScrollTests, MetricsEmittedPerScroll) {
// The counters should have been reset and there wouldn't be any janky frames.
histogram_tester->ExpectBucketCount(kMissedVsyncsSumPerScrollHistogram, 0, 1);
histogram_tester->ExpectBucketCount(kMissedVsyncsSumPerScrollV3Histogram, 0,
1);
histogram_tester->ExpectBucketCount(kMissedVsyncsMaxPerScrollHistogram, 0, 1);
histogram_tester->ExpectBucketCount(kMissedVsyncsPerScrollV3Histogram, 0, 1);
histogram_tester->ExpectBucketCount(kDelayedFramesPerScrollHistogram, 0, 1);
histogram_tester->ExpectBucketCount(kDelayedFramesPerScrollV3Histogram, 0, 1);
}
TEST_P(PerScrollTests, VsyncMetricsEmittedPerScroll) {

@ -61,7 +61,7 @@ class ScrollJankUkmReporterTest : public testing::Test {
/*delta=*/10.0f, first_input_ts, base::TimeTicks(), &tick_clock,
/*trace_id=*/std::nullopt);
scroll_jank_dropped_frame_tracker_->ReportLatestPresentationData(
*event.get(), last_input_ts, presentation_ts, vsync_interval);
*event, *event, last_input_ts, presentation_ts, vsync_interval);
}
void ReportFramesToPredictorJankTracker(double delta,

@ -492,6 +492,71 @@ chromium-metrics-reviews@google.com.
</summary>
</histogram>
<histogram name="Event.ScrollJank.DelayedFramesPercentage.FixedWindow3"
units="%" expires_after="2026-03-17">
<owner>petrcermak@chromium.org</owner>
<owner>kartarsingh@google.com</owner>
<owner>jonross@chromium.org</owner>
<owner>woa-performance-team@google.com</owner>
<summary>
NOTE: This metric is a new experimental version of
Event.ScrollJank.DelayedFramesPercentage.FixedWindow. This new metric aims
to address the old metric's shortcomings and eventually replace it. For now,
we recommend you still use the old metric
(Event.ScrollJank.DelayedFramesPercentage.FixedWindow) for performance
analysis.
The percentage of frames not presented during a scroll or a fling[1],
meaning frame production took longer than expected and the frame was
skipped.
This metric is different from traditional graphics smoothness metrics as it
considers performance degradation on all threads and processes including
Browser and IO, unlike graphics' smoothness metrics which focus on the core
graphics pipeline (Renderer/GPU).
This metric is only recorded when 64 frames are presented, for example if a
scroll only has 12 frames, the next scroll has 40 frames and the scroll
after has 12 frames, the metric only will be emitted after the 3rd scroll.
This means the last incomplete window might not get reported as the browser
terminates.
For more details about this metric, please check
http://doc/1Y0u0Tq5eUZff75nYUzQVw6JxmbZAW9m64pJidmnGWsY
[1]Fling is a finger boosted scroll, meaning the screen is moving after the
user has accelerated their finger on the screen and removed it.
</summary>
</histogram>
<histogram name="Event.ScrollJank.DelayedFramesPercentage.PerScroll3" units="%"
expires_after="2026-03-17">
<owner>petrcermak@chromium.org</owner>
<owner>kartarsingh@google.com</owner>
<owner>jonross@chromium.org</owner>
<owner>woa-performance-team@google.com</owner>
<summary>
NOTE: This metric is a new experimental version of
Event.ScrollJank.DelayedFramesPercentage.PerScroll. This new metric aims to
address the old metric's shortcomings and eventually replace it. For now, we
recommend you still use the old metric
(Event.ScrollJank.DelayedFramesPercentage.PerScroll) for performance
analysis.
A frame is deemed janky during a scroll if there was no presented frame in
the previous vsync, and the current frame contains input that was not
presented during the missed frame. Missed input is determined by comparing
the timestamp of the earliest input of the current frame, to that of the
last input of the previously presented frame. If the delta of the timestamps
is less than a vsync + vsync/2, then the input is considered to have been
intended for the missed frame. This metric reports the percentage of delayed
frames within a scroll.
For more details about this metric, please check
http://doc/1Y0u0Tq5eUZff75nYUzQVw6JxmbZAW9m64pJidmnGWsY
</summary>
</histogram>
<histogram name="Event.ScrollJank.DelayedFramesPercentage.PerScroll{Length}"
units="%" expires_after="2025-08-24">
<owner>kartarsingh@google.com</owner>
@ -559,6 +624,62 @@ chromium-metrics-reviews@google.com.
</summary>
</histogram>
<histogram name="Event.ScrollJank.MissedVsyncsPercentage.FixedWindow3"
units="%" expires_after="2026-03-17">
<owner>petrcermak@chromium.org</owner>
<owner>jonross@chromium.org</owner>
<owner>woa-performance-team@google.com</owner>
<summary>
NOTE: This metric is a new experimental version of
Event.ScrollJank.MissedVsyncsPercentage.FixedWindow. This new metric aims to
address the old metric's shortcomings and eventually replace it. For now, we
recommend you still use the old metric
(Event.ScrollJank.MissedVsyncsPercentage.FixedWindow) for performance
analysis.
A vsync is deemed janky during a scroll if there was no presented frame in
the vsync and the next frame contained late input that was not presented
during the janky vsync. Missed input is determined by comparing the
timestamp of the earliest input of the current frame, to that of the last
input of the previously presented frame. If the delta of the timestamps is
less than a vsync + vsync/2, then the input is considered to have been
intended for the missed frame. This metric reports the percentage of janky
vsyncs within a window of size 64.
For more details about this metric, please check
http://doc/1Y0u0Tq5eUZff75nYUzQVw6JxmbZAW9m64pJidmnGWsY
</summary>
</histogram>
<histogram name="Event.ScrollJank.MissedVsyncsPercentage.PerScroll3" units="%"
expires_after="2026-03-17">
<owner>petrcermak@chromium.org</owner>
<owner>kartarsingh@google.com</owner>
<owner>jonross@chromium.org</owner>
<owner>woa-performance-team@google.com</owner>
<summary>
NOTE: This metric is a new experimental version of
Event.ScrollJank.MissedVsyncsPercentage.PerScroll. This new metric aims to
address the old metric's shortcomings and eventually replace it. For now, we
recommend you still use the old metric
(Event.ScrollJank.MissedVsyncsPercentage.PerScroll) for performance
analysis.
A vsync is deemed janky during a scroll if there was no presented frame in
the vsync and the next frame contained late input that was not presented
during the janky vsync. Missed input is determined by comparing the
timestamp of the earliest input of the current frame, to that of the last
input of the previously presented frame. If the delta of the timestamps is
less than a vsync + vsync/2, then the input is considered to have been
intended for the missed frame. This metric reports percentage of missed
vsyncs in a scroll in scroll. Metric emits zeros as well i.e. the case when
no frames were delayed.
For more details about this metric, please check
http://doc/1Y0u0Tq5eUZff75nYUzQVw6JxmbZAW9m64pJidmnGWsY
</summary>
</histogram>
<histogram name="Event.ScrollJank.MissedVsyncsPercentage.PerScroll{Length}"
units="%" expires_after="2025-08-03">
<owner>kartarsingh@google.com</owner>
@ -584,6 +705,62 @@ chromium-metrics-reviews@google.com.
<token key="Length" variants="ScrollLength"/>
</histogram>
<histogram name="Event.ScrollJank.MissedVsyncsSum.FixedWindow3" units="counts"
expires_after="2026-03-17">
<owner>petrcermak@chromium.org</owner>
<owner>kartarsingh@google.com</owner>
<owner>jonross@chromium.org</owner>
<owner>woa-performance-team@google.com</owner>
<summary>
NOTE: This metric is a new experimental version of
Event.ScrollJank.MissedVsyncsSum.FixedWindow. This new metric aims to
address the old metric's shortcomings and eventually replace it. For now, we
recommend you still use the old metric
(Event.ScrollJank.MissedVsyncsSum.FixedWindow) for performance analysis.
A vsync is deemed janky during a scroll if there was no presented frame in
the vsync and the next frame contained late input that was not presented
during the janky vsync. Missed input is determined by comparing the
timestamp of the earliest input of the current frame, to that of the last
input of the previously presented frame. If the delta of the timestamps is
less than a vsync + vsync/2, then the input is considered to have been
intended for the missed frame. This metric reports the sum of missed vsyncs
in a window of size 64. Metric emits zeros as well i.e. the case when no
frames were delayed.
For more details about this metric, please check
http://doc/1Y0u0Tq5eUZff75nYUzQVw6JxmbZAW9m64pJidmnGWsY
</summary>
</histogram>
<histogram name="Event.ScrollJank.MissedVsyncsSum.PerScroll3" units="counts"
expires_after="2026-03-17">
<owner>petrcermak@chromium.org</owner>
<owner>kartarsingh@google.com</owner>
<owner>jonross@chromium.org</owner>
<owner>woa-performance-team@google.com</owner>
<summary>
NOTE: This metric is a new experimental version of
Event.ScrollJank.MissedVsyncsSum.PerScroll. This new metric aims to address
the old metric's shortcomings and eventually replace it. For now, we
recommend you still use the old metric
(Event.ScrollJank.MissedVsyncsSum.PerScroll) for performance analysis.
A frame is deemed janky during a scroll if there was no presented frame in
the previous vsync, and the current frame contains input that was not
presented during the missed frame. Missed input is determined by comparing
the timestamp of the earliest input of the current frame, to that of the
last input of the previously presented frame. If the delta of the timestamps
is less than a vsync + vsync/2, then the input is considered to have been
intended for the missed frame. This metric reports the sum of missed vsyncs
in a scroll. Metric emits zeros as well i.e. the case when no frames were
delayed.
For more details about this metric, please check
http://doc/1Y0u0Tq5eUZff75nYUzQVw6JxmbZAW9m64pJidmnGWsY
</summary>
</histogram>
<histogram name="Event.ScrollJank.MissedVsyncs{Operator}.FixedWindow"
units="counts" expires_after="2025-08-03">
<owner>kartarsingh@google.com</owner>