[M108 Migration][VD] Avoid pending frame counter becoming negative
[platform/framework/web/chromium-efl.git] / cc / metrics / jank_metrics.cc
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.
4
5 #include "cc/metrics/jank_metrics.h"
6
7 #include <algorithm>
8 #include <memory>
9 #include <string>
10 #include <utility>
11
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"
18
19 namespace cc {
20
21 namespace {
22
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;
28
29 constexpr base::TimeDelta kStaleHistogramMin = base::Microseconds(1);
30 constexpr base::TimeDelta kStaleHistogramMax = base::Milliseconds(1000);
31 constexpr int kStaleHistogramBucketCount = 200;
32
33 constexpr bool IsValidJankThreadType(
34     FrameInfo::SmoothEffectDrivingThread type) {
35   return type == FrameInfo::SmoothEffectDrivingThread::kCompositor ||
36          type == FrameInfo::SmoothEffectDrivingThread::kMain;
37 }
38
39 const char* GetJankThreadTypeName(FrameInfo::SmoothEffectDrivingThread type) {
40   DCHECK(IsValidJankThreadType(type));
41
42   switch (type) {
43     case FrameInfo::SmoothEffectDrivingThread::kCompositor:
44       return "Compositor";
45     case FrameInfo::SmoothEffectDrivingThread::kMain:
46       return "Main";
47     default:
48       NOTREACHED();
49       return "";
50   }
51 }
52
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);
58
59   DCHECK_EQ(thread_type, FrameInfo::SmoothEffectDrivingThread::kCompositor);
60   return static_cast<int>(type) + kBuiltinSequenceNum;
61 }
62
63 int GetIndexForStaleMetric(FrameSequenceTrackerType type) {
64   return static_cast<int>(type);
65 }
66
67 std::string GetJankHistogramName(FrameSequenceTrackerType type,
68                                  const char* thread_name) {
69   return base::StrCat(
70       {"Graphics.Smoothness.Jank.", thread_name, ".",
71        FrameSequenceTracker::GetFrameSequenceTrackerTypeName(type)});
72 }
73
74 std::string GetStaleHistogramName(FrameSequenceTrackerType type) {
75   return base::StrCat(
76       {"Graphics.Smoothness.Stale.",
77        FrameSequenceTracker::GetFrameSequenceTrackerTypeName(type)});
78 }
79
80 std::string GetMaxStaleHistogramName(FrameSequenceTrackerType type) {
81   return base::StrCat(
82       {"Graphics.Smoothness.MaxStale.",
83        FrameSequenceTracker::GetFrameSequenceTrackerTypeName(type)});
84 }
85
86 }  // namespace
87
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));
92 }
93 JankMetrics::~JankMetrics() = default;
94
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
99   // presented.
100   queue_frame_token_and_id_.push({frame_token, sequence_number});
101 }
102
103 void JankMetrics::AddFrameWithNoUpdate(uint32_t sequence_number,
104                                        base::TimeDelta frame_interval) {
105   DCHECK_LE(queue_frame_id_and_interval_.size(), kMaxNoUpdateFrameQueueLength);
106
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});
111
112   // This prevents the no-update frame queue from growing infinitely on an idle
113   // page.
114   if (queue_frame_id_and_interval_.size() > kMaxNoUpdateFrameQueueLength)
115     queue_frame_id_and_interval_.pop();
116 }
117
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;
123
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();
127
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.
132       return;
133     }
134     queue_frame_token_and_id_.pop();
135
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;
140       break;
141     }
142   }
143   // If for any reason the sequence number associated with the
144   // presented_frame_token cannot be identified, then ignore this frame
145   // presentation.
146   if (presented_frame_id == 0)
147     return;
148
149   base::TimeDelta no_update_time;  // The frame time spanned by the frames that
150                                    // have no updates
151
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;
160
161   // Compute the presentation delay contributed by no-update frames that
162   // began BEFORE (i.e. have smaller sequence number than) the current
163   // presented frame.
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;
174     }
175     queue_frame_id_and_interval_.pop();
176   }
177
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);
181
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)
185                                             ? zero_delta
186                                             : current_presentation_timestamp -
187                                                   last_presentation_timestamp_ -
188                                                   no_update_time;
189
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;
199
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).
203   //
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
208   // fluctuations.
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;
213
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;
225     }
226
227     if (!prev_frame_delta_.is_zero() &&
228         current_frame_delta > prev_frame_delta_ + 0.5 * frame_interval) {
229       jank_count_++;
230
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_));
239     }
240   }
241   last_presentation_timestamp_ = current_presentation_timestamp;
242   last_presentation_frame_id_ = presented_frame_id;
243   prev_frame_delta_ = current_frame_delta;
244 }
245
246 void JankMetrics::ReportJankMetrics(int frames_expected) {
247   if (tracker_type_ == FrameSequenceTrackerType::kCustom)
248     return;
249
250   int jank_percent = static_cast<int>(100 * jank_count_ / frames_expected);
251
252   const char* jank_thread_name = GetJankThreadTypeName(effective_thread_);
253
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));
261
262   const bool is_animation =
263       ShouldReportForAnimation(tracker_type_, effective_thread_);
264
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_);
269
270   if (is_animation) {
271     UMA_HISTOGRAM_PERCENTAGE("Graphics.Smoothness.Jank.AllAnimations",
272                              jank_percent);
273   }
274
275   if (is_interaction) {
276     UMA_HISTOGRAM_PERCENTAGE("Graphics.Smoothness.Jank.AllInteractions",
277                              jank_percent);
278   }
279
280   if (is_animation || is_interaction) {
281     UMA_HISTOGRAM_PERCENTAGE("Graphics.Smoothness.Jank.AllSequences",
282                              jank_percent);
283   }
284
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));
294
295   // Reset counts to avoid duplicated reporting.
296   Reset();
297 }
298
299 void JankMetrics::Reset() {
300   jank_count_ = 0;
301   max_staleness_ = {};
302 }
303
304 void JankMetrics::Merge(std::unique_ptr<JankMetrics> jank_metrics) {
305   if (jank_metrics) {
306     jank_count_ += jank_metrics->jank_count_;
307     max_staleness_ = std::max(max_staleness_, jank_metrics->max_staleness_);
308   }
309 }
310
311 }  // namespace cc