1 // Copyright 2020 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "cc/metrics/jank_metrics.h"
12 #include "base/metrics/histogram.h"
13 #include "base/metrics/histogram_macros.h"
14 #include "base/notreached.h"
15 #include "base/strings/strcat.h"
16 #include "base/trace_event/trace_event.h"
17 #include "cc/metrics/frame_sequence_tracker.h"
23 constexpr uint64_t kMaxNoUpdateFrameQueueLength = 100;
24 constexpr int kBuiltinSequenceNum =
25 static_cast<int>(FrameSequenceTrackerType::kMaxType) + 1;
26 constexpr int kMaximumJankHistogramIndex = 2 * kBuiltinSequenceNum;
27 constexpr int kMaximumStaleHistogramIndex = kBuiltinSequenceNum;
29 constexpr base::TimeDelta kStaleHistogramMin = base::Microseconds(1);
30 constexpr base::TimeDelta kStaleHistogramMax = base::Milliseconds(1000);
31 constexpr int kStaleHistogramBucketCount = 200;
33 constexpr bool IsValidJankThreadType(
34 FrameInfo::SmoothEffectDrivingThread type) {
35 return type == FrameInfo::SmoothEffectDrivingThread::kCompositor ||
36 type == FrameInfo::SmoothEffectDrivingThread::kMain;
39 const char* GetJankThreadTypeName(FrameInfo::SmoothEffectDrivingThread type) {
40 DCHECK(IsValidJankThreadType(type));
43 case FrameInfo::SmoothEffectDrivingThread::kCompositor:
45 case FrameInfo::SmoothEffectDrivingThread::kMain:
53 int GetIndexForJankMetric(FrameInfo::SmoothEffectDrivingThread thread_type,
54 FrameSequenceTrackerType type) {
55 DCHECK(IsValidJankThreadType(thread_type));
56 if (thread_type == FrameInfo::SmoothEffectDrivingThread::kMain)
57 return static_cast<int>(type);
59 DCHECK_EQ(thread_type, FrameInfo::SmoothEffectDrivingThread::kCompositor);
60 return static_cast<int>(type) + kBuiltinSequenceNum;
63 int GetIndexForStaleMetric(FrameSequenceTrackerType type) {
64 return static_cast<int>(type);
67 std::string GetJankHistogramName(FrameSequenceTrackerType type,
68 const char* thread_name) {
70 {"Graphics.Smoothness.Jank.", thread_name, ".",
71 FrameSequenceTracker::GetFrameSequenceTrackerTypeName(type)});
74 std::string GetStaleHistogramName(FrameSequenceTrackerType type) {
76 {"Graphics.Smoothness.Stale.",
77 FrameSequenceTracker::GetFrameSequenceTrackerTypeName(type)});
80 std::string GetMaxStaleHistogramName(FrameSequenceTrackerType type) {
82 {"Graphics.Smoothness.MaxStale.",
83 FrameSequenceTracker::GetFrameSequenceTrackerTypeName(type)});
88 JankMetrics::JankMetrics(FrameSequenceTrackerType tracker_type,
89 FrameInfo::SmoothEffectDrivingThread effective_thread)
90 : tracker_type_(tracker_type), effective_thread_(effective_thread) {
91 DCHECK(IsValidJankThreadType(effective_thread));
93 JankMetrics::~JankMetrics() = default;
95 void JankMetrics::AddSubmitFrame(uint32_t frame_token,
96 uint32_t sequence_number) {
97 // When a frame is submitted, record its |frame_token| and its associated
98 // |sequence_number|. This pushed item will be removed when this frame is
100 queue_frame_token_and_id_.push({frame_token, sequence_number});
103 void JankMetrics::AddFrameWithNoUpdate(uint32_t sequence_number,
104 base::TimeDelta frame_interval) {
105 DCHECK_LE(queue_frame_id_and_interval_.size(), kMaxNoUpdateFrameQueueLength);
107 // If a frame does not cause an increase in expected frames, it will be
108 // recorded here and later subtracted from the presentation interval that
109 // includes this frame.
110 queue_frame_id_and_interval_.push({sequence_number, frame_interval});
112 // This prevents the no-update frame queue from growing infinitely on an idle
114 if (queue_frame_id_and_interval_.size() > kMaxNoUpdateFrameQueueLength)
115 queue_frame_id_and_interval_.pop();
118 void JankMetrics::AddPresentedFrame(
119 uint32_t presented_frame_token,
120 base::TimeTicks current_presentation_timestamp,
121 base::TimeDelta frame_interval) {
122 uint32_t presented_frame_id = 0;
124 // Find the main_sequence_number of the presented_frame_token
125 while (!queue_frame_token_and_id_.empty()) {
126 auto token_and_id = queue_frame_token_and_id_.front();
128 if (token_and_id.first > presented_frame_token) {
129 // The submitting of this presented frame was not recorded (e.g. the
130 // submitting might have occurred before JankMetrics starts recording).
131 // In that case, do not use this frame presentation for jank detection.
134 queue_frame_token_and_id_.pop();
136 if (token_and_id.first == presented_frame_token) {
137 // Found information about the submit of this presented frame;
138 // retrieve the frame's sequence number.
139 presented_frame_id = token_and_id.second;
143 // If for any reason the sequence number associated with the
144 // presented_frame_token cannot be identified, then ignore this frame
146 if (presented_frame_id == 0)
149 base::TimeDelta no_update_time; // The frame time spanned by the frames that
152 // If |queue_frame_id_and_interval_| contains an excessive amount of no-update
153 // frames, it indicates that the current presented frame is most likely the
154 // first presentation after a long idle period. Such frames are excluded from
155 // jank/stale calculation because they usually have little impact on
156 // smoothness perception, and |queue_frame_id_and_interval_| does not hold
157 // enough data to accurately estimate the effective frame delta.
158 bool will_ignore_current_frame =
159 queue_frame_id_and_interval_.size() == kMaxNoUpdateFrameQueueLength;
161 // Compute the presentation delay contributed by no-update frames that
162 // began BEFORE (i.e. have smaller sequence number than) the current
164 while (!queue_frame_id_and_interval_.empty() &&
165 queue_frame_id_and_interval_.front().first < presented_frame_id) {
166 auto id_and_interval = queue_frame_id_and_interval_.front();
167 if (id_and_interval.first >= last_presentation_frame_id_) {
168 // Only count no-update frames that began SINCE (i.e. have a greater [or
169 // equal] sequence number than) the beginning of previous presented frame.
170 // If, in rare cases, there are still no-update frames that began BEFORE
171 // the beginning of previous presented frame left in the queue, those
172 // frames will simply be discarded and not counted into |no_update_time|.
173 no_update_time += id_and_interval.second;
175 queue_frame_id_and_interval_.pop();
178 // Exclude the presentation delay introduced by no-update frames. If this
179 // exclusion results in negative frame delta, treat the frame delta as 0.
180 const base::TimeDelta zero_delta = base::Milliseconds(0);
182 // Setting the current_frame_delta to zero conveniently excludes the current
183 // frame to be ignored from jank/stale calculation.
184 base::TimeDelta current_frame_delta = (will_ignore_current_frame)
186 : current_presentation_timestamp -
187 last_presentation_timestamp_ -
190 // Guard against the situation when the physical presentation interval is
191 // shorter than |no_update_time|. For example, consider two BeginFrames A and
192 // B separated by 5 vsync cycles of no-updates (i.e. |no_update_time| = 5
193 // vsync cycles); the Presentation of A occurs 2 vsync cycles after BeginFrame
194 // A, whereas Presentation B occurs in the same vsync cycle as BeginFrame B.
195 // In this situation, the physical presentation interval is shorter than 5
196 // vsync cycles and will result in a negative |current_frame_delta|.
197 if (current_frame_delta < zero_delta)
198 current_frame_delta = zero_delta;
200 // Only start tracking jank if this function has already been
201 // called at least once (so that |last_presentation_timestamp_|
202 // and |prev_frame_delta_| have been set).
204 // The presentation interval is typically a multiple of VSync
205 // intervals (i.e. 16.67ms, 33.33ms, 50ms ... on a 60Hz display)
206 // with small fluctuations. The 0.5 * |frame_interval| criterion
207 // is chosen so that the jank detection is robust to those
209 if (!last_presentation_timestamp_.is_null()) {
210 base::TimeDelta staleness = current_frame_delta - frame_interval;
211 if (staleness < zero_delta)
212 staleness = zero_delta;
214 if (tracker_type_ != FrameSequenceTrackerType::kCustom) {
215 STATIC_HISTOGRAM_POINTER_GROUP(
216 GetStaleHistogramName(tracker_type_),
217 GetIndexForStaleMetric(tracker_type_), kMaximumStaleHistogramIndex,
218 AddTimeMillisecondsGranularity(staleness),
219 base::Histogram::FactoryTimeGet(
220 GetStaleHistogramName(tracker_type_), kStaleHistogramMin,
221 kStaleHistogramMax, kStaleHistogramBucketCount,
222 base::HistogramBase::kUmaTargetedHistogramFlag));
223 if (staleness > max_staleness_)
224 max_staleness_ = staleness;
227 if (!prev_frame_delta_.is_zero() &&
228 current_frame_delta > prev_frame_delta_ + 0.5 * frame_interval) {
231 TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP1(
232 "cc,benchmark", "Jank", TRACE_ID_LOCAL(this),
233 last_presentation_timestamp_, "thread-type",
234 GetJankThreadTypeName(effective_thread_));
235 TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP1(
236 "cc,benchmark", "Jank", TRACE_ID_LOCAL(this),
237 current_presentation_timestamp, "tracker-type",
238 FrameSequenceTracker::GetFrameSequenceTrackerTypeName(tracker_type_));
241 last_presentation_timestamp_ = current_presentation_timestamp;
242 last_presentation_frame_id_ = presented_frame_id;
243 prev_frame_delta_ = current_frame_delta;
246 void JankMetrics::ReportJankMetrics(int frames_expected) {
247 if (tracker_type_ == FrameSequenceTrackerType::kCustom)
250 int jank_percent = static_cast<int>(100 * jank_count_ / frames_expected);
252 const char* jank_thread_name = GetJankThreadTypeName(effective_thread_);
254 STATIC_HISTOGRAM_POINTER_GROUP(
255 GetJankHistogramName(tracker_type_, jank_thread_name),
256 GetIndexForJankMetric(effective_thread_, tracker_type_),
257 kMaximumJankHistogramIndex, Add(jank_percent),
258 base::LinearHistogram::FactoryGet(
259 GetJankHistogramName(tracker_type_, jank_thread_name), 1, 100, 101,
260 base::HistogramBase::kUmaTargetedHistogramFlag));
262 const bool is_animation =
263 ShouldReportForAnimation(tracker_type_, effective_thread_);
265 // Jank reporter's effective thread is guaranteed to be identical to that of
266 // the owning FrameSequenceMetrics instance.
267 const bool is_interaction = ShouldReportForInteraction(
268 tracker_type_, effective_thread_, effective_thread_);
271 UMA_HISTOGRAM_PERCENTAGE("Graphics.Smoothness.Jank.AllAnimations",
275 if (is_interaction) {
276 UMA_HISTOGRAM_PERCENTAGE("Graphics.Smoothness.Jank.AllInteractions",
280 if (is_animation || is_interaction) {
281 UMA_HISTOGRAM_PERCENTAGE("Graphics.Smoothness.Jank.AllSequences",
285 // Report the max staleness metrics
286 STATIC_HISTOGRAM_POINTER_GROUP(
287 GetMaxStaleHistogramName(tracker_type_),
288 GetIndexForStaleMetric(tracker_type_), kMaximumStaleHistogramIndex,
289 AddTimeMillisecondsGranularity(max_staleness_),
290 base::Histogram::FactoryTimeGet(
291 GetMaxStaleHistogramName(tracker_type_), kStaleHistogramMin,
292 kStaleHistogramMax, kStaleHistogramBucketCount,
293 base::HistogramBase::kUmaTargetedHistogramFlag));
295 // Reset counts to avoid duplicated reporting.
299 void JankMetrics::Reset() {
304 void JankMetrics::Merge(std::unique_ptr<JankMetrics> jank_metrics) {
306 jank_count_ += jank_metrics->jank_count_;
307 max_staleness_ = std::max(max_staleness_, jank_metrics->max_staleness_);