1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
cc / metrics / average_lag_tracking_manager_unittest.cc [blame]
// Copyright 2020 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/average_lag_tracking_manager.h"
#include <algorithm>
#include <memory>
#include "base/test/metrics/histogram_tester.h"
#include "components/viz/common/frame_timing_details.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cc {
namespace {
using base::Bucket;
using testing::ElementsAre;
using testing::IsEmpty;
// Helper for TimeTicks usage
base::TimeTicks MillisecondsToTimeTicks(int t_ms) {
return base::TimeTicks() + base::Milliseconds(t_ms);
}
// Helper function returning a successful `FrameTimingDetails` for use in
// `DidPresentCompositorFrame()`.
viz::FrameTimingDetails PrepareFrameDetails(base::TimeTicks swap_time,
base::TimeTicks presentation_time) {
viz::FrameTimingDetails details;
details.swap_timings.swap_start = swap_time;
details.presentation_feedback.timestamp = presentation_time;
return details;
}
// Helper function returning a failed `FrameTimingDetails` for use in
// `DidPresentCompositorFrame()`.
viz::FrameTimingDetails PrepareFailedFrameDetails() {
viz::FrameTimingDetails details;
details.presentation_feedback = gfx::PresentationFeedback::Failure();
return details;
}
class AverageLagTrackingManagerTest : public testing::Test {
protected:
AverageLagTrackingManagerTest() = default;
// Creates a scroll event each |scroll_rate| (in ms) of |scroll_delta| px.
// Collect events at the expected |gpu_swap_times|.
void SimulateConstantScroll(const std::vector<int>& gpu_swap_times,
float scroll_delta,
int scroll_rate,
ui::ScrollInputType scroll_input_type =
ui::ScrollInputType::kTouchscreen) {
if (gpu_swap_times.empty() || gpu_swap_times[0] < scroll_rate)
return;
// Creates 1st frame with scroll begin.
int events_count = gpu_swap_times[0] / scroll_rate;
EventMetricsSet events;
base::TimeTicks event_time = MillisecondsToTimeTicks(scroll_rate);
base::TimeDelta time_to_rwh = base::Milliseconds(1);
events.main_event_metrics.push_back(PrepareScrollUpdateEvent(
ScrollUpdateEventMetrics::ScrollUpdateType::kStarted, event_time,
event_time + time_to_rwh, scroll_delta, scroll_input_type));
for (int i = 1; i < events_count; i++) {
event_time += base::Milliseconds(scroll_rate);
events.main_event_metrics.push_back(PrepareScrollUpdateEvent(
ScrollUpdateEventMetrics::ScrollUpdateType::kContinued, event_time,
event_time + time_to_rwh, scroll_delta, scroll_input_type));
}
average_lag_tracking_manager_.CollectScrollEventsFromFrame(0, events);
// Creates remaining frames.
for (size_t frame = 1; frame < gpu_swap_times.size(); frame++) {
int time_delta = gpu_swap_times[frame] - gpu_swap_times[frame - 1];
events_count = time_delta / scroll_rate;
events.main_event_metrics.clear();
for (int i = 0; i < events_count; i++) {
event_time += base::Milliseconds(scroll_rate);
events.main_event_metrics.push_back(PrepareScrollUpdateEvent(
ScrollUpdateEventMetrics::ScrollUpdateType::kContinued, event_time,
event_time + time_to_rwh, scroll_delta, scroll_input_type));
}
average_lag_tracking_manager_.CollectScrollEventsFromFrame(frame, events);
}
}
// Prepares an `ScrollUpdateEventMetrics` object for a scroll-update event.
std::unique_ptr<ScrollUpdateEventMetrics> PrepareScrollUpdateEvent(
ScrollUpdateEventMetrics::ScrollUpdateType scroll_update_type,
base::TimeTicks event_time,
base::TimeTicks arrived_in_browser_main_timestamp,
float delta,
ui::ScrollInputType scroll_input_type =
ui::ScrollInputType::kTouchscreen) {
const bool kScrollIsNotInertial = false;
const int64_t trace_id = 123;
return ScrollUpdateEventMetrics::Create(
ui::EventType::kGestureScrollUpdate, scroll_input_type,
kScrollIsNotInertial, scroll_update_type, delta, event_time,
arrived_in_browser_main_timestamp, base::TimeTicks(),
base::IdType64<class ui::LatencyInfo>(trace_id));
}
AverageLagTrackingManager average_lag_tracking_manager_;
};
// Ensure that AverageLag metrics are not logged in non-touchscreen scenarios.
TEST_F(AverageLagTrackingManagerTest, EnsureMetricNotLogged) {
base::HistogramTester histogram_tester;
std::vector<int> gpu_swap_times = {400, 1400, 1600};
std::vector<int> presentation_times = {500, 1500, 1700};
SimulateConstantScroll(gpu_swap_times, 10, 100, ui::ScrollInputType::kWheel);
for (size_t frame = 0; frame < gpu_swap_times.size(); frame++) {
average_lag_tracking_manager_.DidPresentCompositorFrame(
frame, PrepareFrameDetails(
MillisecondsToTimeTicks(gpu_swap_times[frame]),
MillisecondsToTimeTicks(presentation_times[frame])));
}
// Checking the 2 histograms should suffice. If they aren't logged, other
// AverageLag metrics also won't be logged.
histogram_tester.ExpectTotalCount(
"Event.Latency.ScrollBegin.Touch.AverageLagPresentation", 0);
histogram_tester.ExpectTotalCount(
"Event.Latency.ScrollUpdate.Touch.AverageLagPresentation", 0);
}
// Simulate a simple situation that generates events at every 10ms starting at
// t=15ms and swaps frames at every 10ms, too, starting at t=20ms. Then tests
// that we record one UMA for ScrollUpdate in one second. Tests usage of
// `CollectScrollEventAtFrame()` (1 event per collection).
TEST_F(AverageLagTrackingManagerTest, OneSecondInterval) {
base::HistogramTester histogram_tester;
const float scroll_delta = 10.0f;
base::TimeTicks event_time = MillisecondsToTimeTicks(5);
base::TimeTicks arrived_in_browser_main_timestamp =
MillisecondsToTimeTicks(7);
base::TimeTicks gpu_swap_time = MillisecondsToTimeTicks(10);
base::TimeTicks presentation_time = MillisecondsToTimeTicks(13);
int frame_id = 1;
// ScrollBegin
event_time += base::Milliseconds(10); // 15ms
arrived_in_browser_main_timestamp += base::Milliseconds(10); // 17ms
gpu_swap_time += base::Milliseconds(10); // 20ms
presentation_time += base::Milliseconds(10); // 23ms
EventMetricsSet events;
events.main_event_metrics.push_back(PrepareScrollUpdateEvent(
ScrollUpdateEventMetrics::ScrollUpdateType::kStarted, event_time,
arrived_in_browser_main_timestamp, scroll_delta));
average_lag_tracking_manager_.CollectScrollEventsFromFrame(frame_id, events);
average_lag_tracking_manager_.DidPresentCompositorFrame(
frame_id, PrepareFrameDetails(gpu_swap_time, presentation_time));
// Send 101 ScrollUpdate events to verify that there is 1 AverageLag recorded
// per 1 second.
const int kUpdates = 101;
for (int i = 0; i < kUpdates; i++) {
event_time += base::Milliseconds(10);
arrived_in_browser_main_timestamp += base::Milliseconds(10);
gpu_swap_time += base::Milliseconds(10);
presentation_time += base::Milliseconds(10);
// First 50 has positive delta, others negative delta.
const int sign = (i < kUpdates / 2) ? 1 : -1;
events.main_event_metrics.clear();
events.main_event_metrics.push_back(PrepareScrollUpdateEvent(
ScrollUpdateEventMetrics::ScrollUpdateType::kContinued, event_time,
arrived_in_browser_main_timestamp, sign * scroll_delta));
average_lag_tracking_manager_.CollectScrollEventsFromFrame(frame_id,
events);
average_lag_tracking_manager_.DidPresentCompositorFrame(
frame_id, PrepareFrameDetails(gpu_swap_time, presentation_time));
}
// ScrollBegin report time is at 20ms, so the next ScrollUpdate report time is
// at 1020ms. The last event_time that finish this report should be later than
// 1020ms.
EXPECT_EQ(event_time, MillisecondsToTimeTicks(1025));
EXPECT_EQ(arrived_in_browser_main_timestamp, MillisecondsToTimeTicks(1027));
EXPECT_EQ(gpu_swap_time, MillisecondsToTimeTicks(1030));
EXPECT_EQ(presentation_time, MillisecondsToTimeTicks(1033));
// Using the presentation time (25ms) instead of gpu swap (20ms) the expected
// finger position is delta = 16px. Then (0.5*(10px+18px)*10ms)/10ms = 14px.
histogram_tester.ExpectBucketCount(
"Event.Latency.ScrollBegin.Touch.AverageLagPresentation", 14, 1);
// As the presentation times are at 80% of the gap between 2 scroll events,
// the Lag Area between 2 frames is defined by the trapezoids: (time=event-2,
// delta=8px), (time=event, delta=10px), (time=event+8, delta=18). This makes
// 99 trapezoids with an area of 0.5*2*(8+10) + 0.5*8*(10+18) = 130px.
// For scroll up/down frame, the Lag at the last frame swap is 2px, and Lag
// at this frame swap is 12px. For the one changing direction, the Lag is
// from 8 to 10 and down to 8 again. So total LagArea is 99 * 130, plus
// 0.5*8*(10+2) + 0.5*2*(8+10) = 66. This makes 12,936, Caled by 1 sec.
histogram_tester.ExpectBucketCount(
"Event.Latency.ScrollUpdate.Touch.AverageLagPresentation", 12.936, 1);
histogram_tester.ExpectBucketCount(
"Event.Latency.ScrollUpdate.Touch.AverageLagPresentation."
"PredictionPositive",
0, 1);
histogram_tester.ExpectTotalCount(
"Event.Latency.ScrollUpdate.Touch.AverageLagPresentation."
"PredictionNegative",
0);
}
// This test creates 3 frames in order to check the submission of ScrollBegin
// and ScrollUpdate events sent using `CollectScrollEventsAtFrame()` (multiple
// events per collection)
TEST_F(AverageLagTrackingManagerTest, MultipleEventsInSameFrame) {
base::HistogramTester histogram_tester;
std::vector<int> gpu_swap_times = {400, 1400, 1600};
std::vector<int> presentation_times = {500, 1500, 1700};
SimulateConstantScroll(gpu_swap_times, 10, 100);
for (size_t frame = 0; frame < gpu_swap_times.size(); frame++) {
average_lag_tracking_manager_.DidPresentCompositorFrame(
frame, PrepareFrameDetails(
MillisecondsToTimeTicks(gpu_swap_times[frame]),
MillisecondsToTimeTicks(presentation_times[frame])));
}
// As the first frame is the ScrollBegin frame, the average lag is, using the
// presentation time, 0.5*(10 + 50) * 40 / 40 = 30.
histogram_tester.ExpectBucketCount(
"Event.Latency.ScrollBegin.Touch.AverageLagPresentation", 30, 1);
// Only the ScrollUpdate events from frame 2 are sent (as the frame 3 is
// waiting for the next frame for sumission).
// As there is a scroll update right at the same time as the frame submission,
// using presentation time, frame 2 starts with 10 lag at 0.5s and finishes
// with 110 at 1.5, thus: 0.5 * (10 + 110) = 60.
histogram_tester.ExpectBucketCount(
"Event.Latency.ScrollUpdate.Touch.AverageLagPresentation", 60, 1);
}
// Tests that if failed presentations arrive out-of-order, they don't mark
// previous pending frames as failed since they can still end up in a
// successful presentation.
TEST_F(AverageLagTrackingManagerTest, OutOfOrderPresentationFeedback) {
base::HistogramTester histogram_tester;
const float scroll_delta = 100.0f;
std::vector<int> event_times = {500, 1500, 2500, 3500};
std::vector<int> arrived_in_browser_main_timestamps = {700, 1700, 2700, 3700};
std::vector<int> gpu_swap_times = {900, 1900, 2900, 3900};
std::vector<int> presentation_times = {1000, 2000, 3000, 4000};
// Create a scroll-begin event. Submit frame 0 with updates from scroll-begin
// event and present it successfully. No AverageLag metrics should be reported
// yet.
EventMetricsSet events;
events.main_event_metrics.push_back(PrepareScrollUpdateEvent(
ScrollUpdateEventMetrics::ScrollUpdateType::kStarted,
MillisecondsToTimeTicks(event_times[0]),
MillisecondsToTimeTicks(arrived_in_browser_main_timestamps[0]),
scroll_delta));
average_lag_tracking_manager_.CollectScrollEventsFromFrame(0, events);
average_lag_tracking_manager_.DidPresentCompositorFrame(
0, PrepareFrameDetails(MillisecondsToTimeTicks(gpu_swap_times[0]),
MillisecondsToTimeTicks(presentation_times[0])));
histogram_tester.ExpectTotalCount(
"Event.Latency.ScrollBegin.Touch.AverageLagPresentation", 0);
histogram_tester.ExpectTotalCount(
"Event.Latency.ScrollUpdate.Touch.AverageLagPresentation", 0);
// Create the first scroll-update event. Submit frame 1 with updates from the
// first scroll-update event, but don't present it yet. No AverageLag metrics
// should be recorded.
events.main_event_metrics.clear();
events.main_event_metrics.push_back(PrepareScrollUpdateEvent(
ScrollUpdateEventMetrics::ScrollUpdateType::kContinued,
MillisecondsToTimeTicks(event_times[1]),
MillisecondsToTimeTicks(arrived_in_browser_main_timestamps[1]),
scroll_delta));
average_lag_tracking_manager_.CollectScrollEventsFromFrame(1, events);
histogram_tester.ExpectTotalCount(
"Event.Latency.ScrollBegin.Touch.AverageLagPresentation", 0);
histogram_tester.ExpectTotalCount(
"Event.Latency.ScrollUpdate.Touch.AverageLagPresentation", 0);
// Create the second scroll-update event. Submit frame 2 with updates from the
// second scroll-update event, but fail to present it. No AverageLag metrics
// should be reported.
events.main_event_metrics.clear();
events.main_event_metrics.push_back(PrepareScrollUpdateEvent(
ScrollUpdateEventMetrics::ScrollUpdateType::kContinued,
MillisecondsToTimeTicks(event_times[2]),
MillisecondsToTimeTicks(arrived_in_browser_main_timestamps[2]),
scroll_delta));
average_lag_tracking_manager_.CollectScrollEventsFromFrame(2, events);
average_lag_tracking_manager_.DidPresentCompositorFrame(
2, PrepareFailedFrameDetails());
histogram_tester.ExpectTotalCount(
"Event.Latency.ScrollBegin.Touch.AverageLagPresentation", 0);
histogram_tester.ExpectTotalCount(
"Event.Latency.ScrollUpdate.Touch.AverageLagPresentation", 0);
// Now present frame 1 successfully. This should report AverageLag metrics for
// scroll-begin event of frame 0.
average_lag_tracking_manager_.DidPresentCompositorFrame(
1, PrepareFrameDetails(MillisecondsToTimeTicks(gpu_swap_times[1]),
MillisecondsToTimeTicks(presentation_times[1])));
histogram_tester.ExpectBucketCount(
"Event.Latency.ScrollBegin.Touch.AverageLagPresentation", 125, 1);
histogram_tester.ExpectTotalCount(
"Event.Latency.ScrollUpdate.Touch.AverageLagPresentation", 0);
// Create the third scroll-update event. Submit frame 3 with updates from the
// second scroll-update (which failed to be presented in frame 2) and the
// third scroll-update events. Since the failure of frame 2 should not have
// affected events from frame 1, AverageLag metrics for scroll-update event of
// frame 1 should be reported.
events.main_event_metrics.push_back(PrepareScrollUpdateEvent(
ScrollUpdateEventMetrics::ScrollUpdateType::kContinued,
MillisecondsToTimeTicks(event_times[3]),
MillisecondsToTimeTicks(arrived_in_browser_main_timestamps[3]),
scroll_delta));
average_lag_tracking_manager_.CollectScrollEventsFromFrame(3, events);
average_lag_tracking_manager_.DidPresentCompositorFrame(
3, PrepareFrameDetails(MillisecondsToTimeTicks(gpu_swap_times[3]),
MillisecondsToTimeTicks(presentation_times[3])));
histogram_tester.ExpectBucketCount(
"Event.Latency.ScrollBegin.Touch.AverageLagPresentation", 125, 1);
histogram_tester.ExpectBucketCount(
"Event.Latency.ScrollUpdate.Touch.AverageLagPresentation", 100, 1);
}
} // namespace
} // namespace cc