Implement new Scroll jank metric.
This CL adds logic to track missed frames and vsyncs as discussed
in the [doc](http://doc/1Y0u0Tq5eUZff75nYUzQVw6JxmbZAW9m64pJidmnGWsY)
We emit two separate histograms:
1) Event.Latency.ScrollUpdate.DelayedFramesPercentage
Tracks no. of delayed frames.
2) Event.Latency.ScrollUpdate.MissedVsyncCount
Tracks no. of vsyncs the frames were delayed by.
The histograms gets emitted every `kHistogramEmitFrequency` reported frames.
Bug: b/276722271
Change-Id: I3a96f632e85aa03c407f2f7797674003897cdfa6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4430615
Reviewed-by: Jonathan Ross <jonross@chromium.org>
Reviewed-by: Omar Elmekkawy <mekk@chromium.org>
Commit-Queue: Kartar Singh <kartarsingh@google.com>
Reviewed-by: Stephen Nusko <nuskos@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1146009}
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index 3d10a7e..c55566a 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -205,6 +205,8 @@
"metrics/lcd_text_metrics_reporter.h",
"metrics/predictor_jank_tracker.cc",
"metrics/predictor_jank_tracker.h",
+ "metrics/scroll_jank_dropped_frame_tracker.cc",
+ "metrics/scroll_jank_dropped_frame_tracker.h",
"metrics/shared_metrics_buffer.h",
"metrics/total_frame_counter.cc",
"metrics/total_frame_counter.h",
@@ -756,6 +758,7 @@
"metrics/jank_injector_unittest.cc",
"metrics/jank_metrics_unittest.cc",
"metrics/predictor_jank_tracker_unittest.cc",
+ "metrics/scroll_jank_dropped_frame_tracker_unittest.cc",
"metrics/total_frame_counter_unittest.cc",
"metrics/video_playback_roughness_reporter_unittest.cc",
"mojo_embedder/async_layer_tree_frame_sink_unittest.cc",
diff --git a/cc/metrics/compositor_frame_reporter.cc b/cc/metrics/compositor_frame_reporter.cc
index d1060bad..f4bec63 100644
--- a/cc/metrics/compositor_frame_reporter.cc
+++ b/cc/metrics/compositor_frame_reporter.cc
@@ -1359,13 +1359,22 @@
float total_predicted_delta = 0;
bool had_gesture_scrolls = false;
+ base::TimeTicks input_generation_ts = base::TimeTicks::Max();
+ base::TimeTicks last_coalesced_ts = base::TimeTicks::Min();
for (const auto& event : events_metrics_) {
TRACE_EVENT("input", "GestureType", "gesture", event->type());
switch (event->type()) {
+ // TODO(kartarsingh): Clean up common logic here. Related discussion:
+ // https://crrev.com/c/4430615/19/cc/metrics/compositor_frame_reporter.cc#1393
case EventMetrics::EventType::kGestureScrollUpdate:
normal_input_count += event->AsScrollUpdate()->coalesced_event_count();
total_predicted_delta += event->AsScrollUpdate()->predicted_delta();
had_gesture_scrolls = true;
+ input_generation_ts = std::min(
+ input_generation_ts, event->GetDispatchStageTimestamp(
+ EventMetrics::DispatchStage::kGenerated));
+ last_coalesced_ts = std::max(last_coalesced_ts,
+ event->AsScrollUpdate()->last_timestamp());
break;
case EventMetrics::EventType::kFirstGestureScrollUpdate:
normal_input_count += event->AsScrollUpdate()->coalesced_event_count();
@@ -1375,16 +1384,33 @@
->ResetCurrentScrollReporting();
}
had_gesture_scrolls = true;
+ input_generation_ts = std::min(
+ input_generation_ts, event->GetDispatchStageTimestamp(
+ EventMetrics::DispatchStage::kGenerated));
+ last_coalesced_ts = std::max(last_coalesced_ts,
+ event->AsScrollUpdate()->last_timestamp());
break;
case EventMetrics::EventType::kInertialGestureScrollUpdate:
fling_input_count += event->AsScrollUpdate()->coalesced_event_count();
total_predicted_delta += event->AsScrollUpdate()->predicted_delta();
had_gesture_scrolls = true;
+ input_generation_ts = std::min(
+ input_generation_ts, event->GetDispatchStageTimestamp(
+ EventMetrics::DispatchStage::kGenerated));
+ last_coalesced_ts = std::max(last_coalesced_ts,
+ event->AsScrollUpdate()->last_timestamp());
break;
default:
continue;
}
}
+ // To handle web test failures which causes an event to be coalesced with an
+ // event having null(0) timestamp.
+ // TODO(b/276722271) : Investigate if this needs to be fixed on test side or
+ // if this is a valid scenario.
+ if (last_coalesced_ts.is_null()) {
+ last_coalesced_ts = input_generation_ts;
+ }
TRACE_EVENT("input,input.scrolling", "PresentedFrameInformation",
[events_metrics = std::cref(events_metrics_), fling_input_count,
@@ -1393,11 +1419,20 @@
normal_input_count, ctx);
});
+ if (!had_gesture_scrolls) {
+ return;
+ }
+
const auto end_timestamp = viz_breakdown_.presentation_feedback.timestamp;
- if (had_gesture_scrolls && global_trackers_.predictor_jank_tracker) {
+ if (global_trackers_.predictor_jank_tracker) {
global_trackers_.predictor_jank_tracker->ReportLatestScrollDelta(
total_predicted_delta, end_timestamp, args_.interval);
}
+ if (global_trackers_.scroll_jank_dropped_frame_tracker) {
+ global_trackers_.scroll_jank_dropped_frame_tracker
+ ->ReportLatestPresentationData(input_generation_ts, last_coalesced_ts,
+ end_timestamp, args_.interval);
+ }
}
void CompositorFrameReporter::ReportEventLatencyTraceEvents() const {
diff --git a/cc/metrics/compositor_frame_reporter.h b/cc/metrics/compositor_frame_reporter.h
index 3de2b27..2882003 100644
--- a/cc/metrics/compositor_frame_reporter.h
+++ b/cc/metrics/compositor_frame_reporter.h
@@ -23,6 +23,7 @@
#include "cc/metrics/frame_info.h"
#include "cc/metrics/frame_sequence_metrics.h"
#include "cc/metrics/predictor_jank_tracker.h"
+#include "cc/metrics/scroll_jank_dropped_frame_tracker.h"
#include "cc/scheduler/scheduler.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"
#include "components/viz/common/frame_timing_details.h"
@@ -45,6 +46,8 @@
raw_ptr<EventLatencyTracker, DanglingUntriaged> event_latency_tracker =
nullptr;
raw_ptr<PredictorJankTracker> predictor_jank_tracker = nullptr;
+ raw_ptr<ScrollJankDroppedFrameTracker> scroll_jank_dropped_frame_tracker =
+ nullptr;
};
// This is used for tracing and reporting the duration of pipeline stages within
diff --git a/cc/metrics/compositor_frame_reporting_controller.cc b/cc/metrics/compositor_frame_reporting_controller.cc
index 56acbe0..8184e3f5 100644
--- a/cc/metrics/compositor_frame_reporting_controller.cc
+++ b/cc/metrics/compositor_frame_reporting_controller.cc
@@ -13,6 +13,7 @@
#include "cc/metrics/dropped_frame_counter.h"
#include "cc/metrics/frame_sequence_tracker_collection.h"
#include "cc/metrics/latency_ukm_reporter.h"
+#include "cc/metrics/scroll_jank_dropped_frame_tracker.h"
#include "components/viz/common/frame_timing_details.h"
#include "components/viz/common/quads/compositor_frame_metadata.h"
#include "services/tracing/public/cpp/perfetto/macros.h"
@@ -40,6 +41,8 @@
layer_tree_host_id_(layer_tree_host_id),
latency_ukm_reporter_(std::make_unique<LatencyUkmReporter>()),
predictor_jank_tracker_(std::make_unique<PredictorJankTracker>()),
+ scroll_jank_dropped_frame_tracker_(
+ std::make_unique<ScrollJankDroppedFrameTracker>()),
previous_latency_predictions_main_(base::Microseconds(-1)),
previous_latency_predictions_impl_(base::Microseconds(-1)),
event_latency_predictions_(
@@ -51,6 +54,8 @@
global_trackers_.latency_ukm_reporter = latency_ukm_reporter_.get();
}
global_trackers_.predictor_jank_tracker = predictor_jank_tracker_.get();
+ global_trackers_.scroll_jank_dropped_frame_tracker =
+ scroll_jank_dropped_frame_tracker_.get();
}
CompositorFrameReportingController::~CompositorFrameReportingController() {
diff --git a/cc/metrics/compositor_frame_reporting_controller.h b/cc/metrics/compositor_frame_reporting_controller.h
index cf41349b..4eef09e 100644
--- a/cc/metrics/compositor_frame_reporting_controller.h
+++ b/cc/metrics/compositor_frame_reporting_controller.h
@@ -18,6 +18,7 @@
#include "cc/metrics/event_metrics.h"
#include "cc/metrics/frame_sequence_metrics.h"
#include "cc/metrics/predictor_jank_tracker.h"
+#include "cc/metrics/scroll_jank_dropped_frame_tracker.h"
namespace viz {
struct FrameTimingDetails;
@@ -200,6 +201,8 @@
// must outlive the objects in |submitted_compositor_frames_|.
std::unique_ptr<LatencyUkmReporter> latency_ukm_reporter_;
std::unique_ptr<PredictorJankTracker> predictor_jank_tracker_;
+ std::unique_ptr<ScrollJankDroppedFrameTracker>
+ scroll_jank_dropped_frame_tracker_;
std::unique_ptr<CompositorFrameReporter>
reporters_[PipelineStage::kNumPipelineStages];
diff --git a/cc/metrics/scroll_jank_dropped_frame_tracker.cc b/cc/metrics/scroll_jank_dropped_frame_tracker.cc
new file mode 100644
index 0000000..0ea6a79
--- /dev/null
+++ b/cc/metrics/scroll_jank_dropped_frame_tracker.cc
@@ -0,0 +1,78 @@
+// 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/scroll_jank_dropped_frame_tracker.h"
+
+#include <algorithm>
+
+#include "base/metrics/histogram_macros.h"
+#include "base/trace_event/common/trace_event_common.h"
+#include "base/trace_event/trace_event.h"
+#include "base/trace_event/trace_id_helper.h"
+#include "base/trace_event/typed_macros.h"
+
+namespace cc {
+
+ScrollJankDroppedFrameTracker::ScrollJankDroppedFrameTracker() = default;
+ScrollJankDroppedFrameTracker::~ScrollJankDroppedFrameTracker() = default;
+
+void ScrollJankDroppedFrameTracker::EmitHistogramsAndResetCounters() {
+ DCHECK_EQ(num_presented_frames_, kHistogramEmitFrequency);
+
+ UMA_HISTOGRAM_PERCENTAGE(kDelayedFramesHistogram,
+ (100 * missed_frames_) / kHistogramEmitFrequency);
+ UMA_HISTOGRAM_CUSTOM_COUNTS(kMissedVsyncsHistogram, missed_vsyncs_, 1, 50,
+ 25);
+
+ missed_frames_ = 0;
+ missed_vsyncs_ = 0;
+ // We don't need to reset it to -1 because after the first window we always
+ // have a valid previous frame data to compare the first frame of window.
+ num_presented_frames_ = 0;
+}
+
+void ScrollJankDroppedFrameTracker::ReportLatestPresentationData(
+ base::TimeTicks first_input_generation_ts,
+ base::TimeTicks last_input_generation_ts,
+ base::TimeTicks presentation_ts,
+ base::TimeDelta vsync_interval) {
+ DCHECK_GE(last_input_generation_ts, first_input_generation_ts);
+ DCHECK_GT(presentation_ts, last_input_generation_ts);
+ // TODO(b/276722271) : Analyze and reduce these cases of out of order
+ // frame termination.
+ if (presentation_ts <= prev_presentation_ts_) {
+ TRACE_EVENT_INSTANT("input", "OutOfOrderTerminatedFrame");
+ return;
+ }
+
+ // 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_ts - prev_last_input_generation_ts_) <
+ (vsync_interval + vsync_interval / 2);
+ if (missed_frame && input_available) {
+ missed_frames_++;
+ missed_vsyncs_ +=
+ (presentation_ts - prev_presentation_ts_ - (vsync_interval / 2)) /
+ vsync_interval;
+ TRACE_EVENT_INSTANT("input", "MissedFrame", "missed_frames_",
+ missed_frames_, "missed_vsyncs_", missed_vsyncs_,
+ "vsync_interval", vsync_interval);
+ }
+
+ ++num_presented_frames_;
+ if (num_presented_frames_ == kHistogramEmitFrequency) {
+ EmitHistogramsAndResetCounters();
+ }
+ DCHECK_LT(num_presented_frames_, kHistogramEmitFrequency);
+
+ prev_presentation_ts_ = presentation_ts;
+ prev_last_input_generation_ts_ = last_input_generation_ts;
+}
+
+} // namespace cc
diff --git a/cc/metrics/scroll_jank_dropped_frame_tracker.h b/cc/metrics/scroll_jank_dropped_frame_tracker.h
new file mode 100644
index 0000000..56b0c99
--- /dev/null
+++ b/cc/metrics/scroll_jank_dropped_frame_tracker.h
@@ -0,0 +1,55 @@
+// 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.
+
+#ifndef CC_METRICS_SCROLL_JANK_DROPPED_FRAME_TRACKER_H_
+#define CC_METRICS_SCROLL_JANK_DROPPED_FRAME_TRACKER_H_
+
+#include "base/time/time.h"
+#include "cc/cc_export.h"
+
+namespace cc {
+
+class CC_EXPORT ScrollJankDroppedFrameTracker {
+ public:
+ ScrollJankDroppedFrameTracker();
+ ~ScrollJankDroppedFrameTracker();
+
+ ScrollJankDroppedFrameTracker(const ScrollJankDroppedFrameTracker&) = delete;
+
+ void ReportLatestPresentationData(base::TimeTicks first_input_generation_ts,
+ base::TimeTicks last_input_generation_ts,
+ base::TimeTicks presentation_ts,
+ base::TimeDelta vsync_interval);
+
+ static constexpr int kHistogramEmitFrequency = 64;
+ static constexpr const char* kDelayedFramesHistogram =
+ "Event.Jank.DelayedFramesPercentage";
+ static constexpr const char* kMissedVsyncsHistogram =
+ "Event.Jank.MissedVsyncCount";
+
+ private:
+ void EmitHistogramsAndResetCounters();
+
+ // 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
+ // metric.
+ base::TimeTicks prev_presentation_ts_;
+ base::TimeTicks prev_last_input_generation_ts_;
+
+ // 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;
+
+ // Not initializing with 0 because the first frame in first window will be
+ // always deemed non-janky which makes the metric slightly biased. Setting
+ // it to -1 essentially ignores first frame.
+ int num_presented_frames_ = -1;
+};
+
+} // namespace cc
+
+#endif // CC_METRICS_SCROLL_JANK_DROPPED_FRAME_TRACKER_H_
diff --git a/cc/metrics/scroll_jank_dropped_frame_tracker_unittest.cc b/cc/metrics/scroll_jank_dropped_frame_tracker_unittest.cc
new file mode 100644
index 0000000..d3b2a3e
--- /dev/null
+++ b/cc/metrics/scroll_jank_dropped_frame_tracker_unittest.cc
@@ -0,0 +1,198 @@
+// 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/scroll_jank_dropped_frame_tracker.h"
+
+#include <memory>
+#include <vector>
+
+#include "base/test/metrics/histogram_tester.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+
+namespace {
+
+constexpr base::TimeDelta kVsyncInterval = base::Milliseconds(16);
+
+struct FrameTimestamps {
+ base::TimeTicks first_input_ts;
+ base::TimeTicks last_input_ts;
+ base::TimeTicks presentation_ts;
+
+ FrameTimestamps(base::TimeTicks first_input,
+ base::TimeTicks last_input,
+ base::TimeTicks presentation)
+ : first_input_ts(first_input),
+ last_input_ts(last_input),
+ presentation_ts(presentation) {}
+
+ FrameTimestamps(int first_input, int last_input, int presentation)
+ : 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)) {}
+};
+
+constexpr int kHistogramEmitFrequency =
+ ScrollJankDroppedFrameTracker::kHistogramEmitFrequency;
+constexpr int kFirstWindowSize = kHistogramEmitFrequency + 1;
+constexpr const char* kDelayedFramesHistogram =
+ ScrollJankDroppedFrameTracker::kDelayedFramesHistogram;
+constexpr const char* kMissedVsyncsHistogram =
+ ScrollJankDroppedFrameTracker::kMissedVsyncsHistogram;
+} // namespace
+
+class ScrollJankDroppedFrameTrackerTest : public testing::Test {
+ public:
+ ScrollJankDroppedFrameTrackerTest() = default;
+
+ void SetUp() override {
+ histogram_tester = std::make_unique<base::HistogramTester>();
+ scroll_jank_dropped_frame_tracker_ =
+ std::make_unique<ScrollJankDroppedFrameTracker>();
+ }
+
+ FrameTimestamps ProduceAndReportMockFrames(FrameTimestamps prev_frame,
+ int num_frames) {
+ for (int i = 1; i <= num_frames; i++) {
+ prev_frame.first_input_ts += kVsyncInterval;
+ prev_frame.last_input_ts += kVsyncInterval;
+ prev_frame.presentation_ts += kVsyncInterval;
+
+ scroll_jank_dropped_frame_tracker_->ReportLatestPresentationData(
+ prev_frame.first_input_ts, prev_frame.last_input_ts,
+ prev_frame.presentation_ts, kVsyncInterval);
+ }
+ return prev_frame;
+ }
+
+ void ReportLatestPresentationDataToTracker(const FrameTimestamps& frame) {
+ scroll_jank_dropped_frame_tracker_->ReportLatestPresentationData(
+ frame.first_input_ts, frame.last_input_ts, frame.presentation_ts,
+ kVsyncInterval);
+ }
+
+ std::unique_ptr<base::HistogramTester> histogram_tester;
+
+ private:
+ std::unique_ptr<ScrollJankDroppedFrameTracker>
+ scroll_jank_dropped_frame_tracker_;
+};
+
+TEST_F(ScrollJankDroppedFrameTrackerTest, EmitsHistograms) {
+ FrameTimestamps f1 = {103, 103, 148};
+
+ FrameTimestamps last_frame =
+ ProduceAndReportMockFrames(f1, kHistogramEmitFrequency);
+
+ histogram_tester->ExpectUniqueSample(kDelayedFramesHistogram, 0, 0);
+ histogram_tester->ExpectUniqueSample(kMissedVsyncsHistogram, 0, 0);
+
+ // For first window we emit histogram at 65th reported frame.
+ last_frame = ProduceAndReportMockFrames(last_frame, 1);
+
+ histogram_tester->ExpectUniqueSample(kDelayedFramesHistogram, 0, 1);
+ histogram_tester->ExpectUniqueSample(kMissedVsyncsHistogram, 0, 1);
+
+ // For subsequent windows we emit histogram every 64 frames.
+ ProduceAndReportMockFrames(last_frame, kHistogramEmitFrequency);
+
+ histogram_tester->ExpectUniqueSample(kDelayedFramesHistogram, 0, 2);
+ histogram_tester->ExpectUniqueSample(kMissedVsyncsHistogram, 0, 2);
+}
+
+/*
+Test that regular frame production doesn't cause missed frames.
+vsync v0 v1
+ | |
+input I0 I1 I2 I3
+ | | | |
+F1: |---------------| {I0, I1}
+F2: |---------------| {I2, I3}
+ */
+TEST_F(ScrollJankDroppedFrameTrackerTest, FrameProducedEveryVsync) {
+ const std::vector<int> inputs = {103, 111, 119, 127};
+ const std::vector<int> vsyncs = {148, 164};
+
+ FrameTimestamps f1 = {inputs[0], inputs[1], vsyncs[0]};
+ FrameTimestamps f2 = {inputs[2], inputs[3], vsyncs[1]};
+
+ ReportLatestPresentationDataToTracker(f1);
+ ReportLatestPresentationDataToTracker(f2);
+
+ // To trigger histogram emission.
+ int frames_to_emit_histogram = kFirstWindowSize - 2;
+ ProduceAndReportMockFrames(f2, frames_to_emit_histogram);
+
+ histogram_tester->ExpectUniqueSample(kDelayedFramesHistogram, 0, 1);
+ histogram_tester->ExpectUniqueSample(kMissedVsyncsHistogram, 0, 1);
+}
+
+/*
+Test that sporadic input timing doesn't cause missed frames when no
+frame is expected.
+vsync v0 v1
+ | | | |
+input I0 I1 I2 I3
+ | | | |
+F1: |-------------------| {I0, I1}
+F2: |---------------------| {I2, I3}
+ */
+TEST_F(ScrollJankDroppedFrameTrackerTest, NoFrameProducedForMissingInput) {
+ const std::vector<int> inputs = {103, 111, 135, 143};
+ const std::vector<int> vsyncs = {148, 180};
+
+ FrameTimestamps f1 = {103, 111, 148};
+ FrameTimestamps f2 = {135, 143, 180};
+
+ ReportLatestPresentationDataToTracker(f1);
+ ReportLatestPresentationDataToTracker(f2);
+
+ // To trigger histogram emission.
+ int frames_to_emit_histogram = kFirstWindowSize - 2;
+ ProduceAndReportMockFrames(f2, frames_to_emit_histogram);
+
+ histogram_tester->ExpectUniqueSample(kDelayedFramesHistogram, 0, 1);
+ histogram_tester->ExpectUniqueSample(kMissedVsyncsHistogram, 0, 1);
+}
+
+/*
+Test that when a frame took too long to be produced shows up in the metric.
+vsync v0 v1
+ | | |
+input I0 I1 I2 I3
+ | | | |
+F1: |---------------| {I0, I1}
+F2: |-----------------------| {I2, I3}
+ */
+TEST_F(ScrollJankDroppedFrameTrackerTest, MissedVsyncWhenInputWasPresent) {
+ const std::vector<int> inputs = {103, 111, 119, 127};
+ const std::vector<int> vsyncs = {148, 196};
+
+ FrameTimestamps f1 = {inputs[0], inputs[1], vsyncs[0]};
+ FrameTimestamps f2 = {inputs[2], inputs[3], vsyncs[1]};
+
+ ReportLatestPresentationDataToTracker(f1);
+ ReportLatestPresentationDataToTracker(f2);
+
+ // To trigger histogram emission.
+ int frames_to_emit_histogram = kFirstWindowSize - 2;
+ FrameTimestamps last_frame_ts =
+ ProduceAndReportMockFrames(f2, frames_to_emit_histogram);
+
+ int expected_missed_frames = 1;
+ int expected_bucket =
+ (100 * expected_missed_frames) / kHistogramEmitFrequency;
+ histogram_tester->ExpectUniqueSample(kDelayedFramesHistogram, expected_bucket,
+ 1);
+ histogram_tester->ExpectUniqueSample(kMissedVsyncsHistogram, 2, 1);
+
+ // The counters were reset for next set of `kHistogramEmitFrequency` frames.
+ ProduceAndReportMockFrames(last_frame_ts, kHistogramEmitFrequency);
+
+ histogram_tester->ExpectBucketCount(kDelayedFramesHistogram, 0, 1);
+ histogram_tester->ExpectBucketCount(kMissedVsyncsHistogram, 0, 1);
+}
+
+} // namespace cc
diff --git a/tools/metrics/histograms/metadata/event/histograms.xml b/tools/metrics/histograms/metadata/event/histograms.xml
index 8c13541..4d79815 100644
--- a/tools/metrics/histograms/metadata/event/histograms.xml
+++ b/tools/metrics/histograms/metadata/event/histograms.xml
@@ -202,6 +202,40 @@
</token>
</histogram>
+<histogram name="Event.Jank.DelayedFramesPercentage" units="%"
+ expires_after="2024-03-20">
+ <owner>kartarsingh@google.com</owner>
+ <owner>mekk@google.com</owner>
+ <owner>woa-performance-team@google.com</owner>
+ <summary>
+ A frame is deemed janky during a scroll if there was no presented frame in
+ previous vsync while the delta in timestamp of earliest input of current
+ frame and timestamp of last input of previous frame is less than vsync. This
+ metric reports the percentage of delayed frames within an arbitrary window
+ of 64 frames.
+
+ For more details about this metric, please check
+ http://doc/1Y0u0Tq5eUZff75nYUzQVw6JxmbZAW9m64pJidmnGWsY
+ </summary>
+</histogram>
+
+<histogram name="Event.Jank.MissedVsyncCount" units="counts"
+ expires_after="2024-03-20">
+ <owner>kartarsingh@google.com</owner>
+ <owner>mekk@google.com</owner>
+ <owner>woa-performance-team@google.com</owner>
+ <summary>
+ A frame is deemed janky during a scroll if there was no presented frame in
+ previous vsync while the delta in timestamp of earliest input of current
+ frame and timestamp of last input of previous frame is less than vsync. For
+ janky frames, this metric counts number of vsyncs the frame was delayed by.
+ The metric gets reported for a windows of 64 reported frames.
+
+ For more details about this metric, please check
+ http://doc/1Y0u0Tq5eUZff75nYUzQVw6JxmbZAW9m64pJidmnGWsY
+ </summary>
+</histogram>
+
<histogram name="Event.Jank.PredictorJankyFramePercentage" units="%"
expires_after="2024-03-20">
<owner>mekk@google.com</owner>