blob: fc39235a93772402d26cc96e6f0cb95d42615a79 [file] [log] [blame]
Kevin DuBois1678e2c2019-08-22 12:26:24 -07001/*
2 * Copyright 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Ady Abrahamb0dbdaa2020-01-06 16:19:42 -080017// TODO(b/129481165): remove the #pragma below and fix conversion issues
18#pragma clang diagnostic push
19#pragma clang diagnostic ignored "-Wconversion"
20
Kevin DuBois1678e2c2019-08-22 12:26:24 -070021#undef LOG_TAG
22#define LOG_TAG "LibSurfaceFlingerUnittests"
23#define LOG_NDEBUG 0
24
25#include "Scheduler/VSyncPredictor.h"
26
27#include <gmock/gmock.h>
28#include <gtest/gtest.h>
29#include <algorithm>
30#include <chrono>
31#include <utility>
32
33using namespace testing;
34using namespace std::literals;
35
36namespace android::scheduler {
37
38MATCHER_P2(IsCloseTo, value, tolerance, "is within tolerance") {
Ady Abraham92fa2f42020-02-11 15:33:56 -080039 return arg <= value + tolerance && arg >= value - tolerance;
Kevin DuBois1678e2c2019-08-22 12:26:24 -070040}
41
42std::vector<nsecs_t> generateVsyncTimestamps(size_t count, nsecs_t period, nsecs_t bias) {
43 std::vector<nsecs_t> vsyncs(count);
44 std::generate(vsyncs.begin(), vsyncs.end(),
45 [&, n = 0]() mutable { return n++ * period + bias; });
46 return vsyncs;
47}
48
49struct VSyncPredictorTest : testing::Test {
50 nsecs_t mNow = 0;
51 nsecs_t mPeriod = 1000;
52 static constexpr size_t kHistorySize = 10;
53 static constexpr size_t kMinimumSamplesForPrediction = 6;
54 static constexpr size_t kOutlierTolerancePercent = 25;
55 static constexpr nsecs_t mMaxRoundingError = 100;
56
57 VSyncPredictor tracker{mPeriod, kHistorySize, kMinimumSamplesForPrediction,
58 kOutlierTolerancePercent};
59};
60
61TEST_F(VSyncPredictorTest, reportsAnticipatedPeriod) {
62 auto [slope, intercept] = tracker.getVSyncPredictionModel();
63
64 EXPECT_THAT(slope, Eq(mPeriod));
65 EXPECT_THAT(intercept, Eq(0));
66
67 auto const changedPeriod = 2000;
68 tracker.setPeriod(changedPeriod);
69 std::tie(slope, intercept) = tracker.getVSyncPredictionModel();
70 EXPECT_THAT(slope, Eq(changedPeriod));
71 EXPECT_THAT(intercept, Eq(0));
72}
73
74TEST_F(VSyncPredictorTest, reportsSamplesNeededWhenHasNoDataPoints) {
75 for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
76 EXPECT_TRUE(tracker.needsMoreSamples(mNow += mPeriod));
77 tracker.addVsyncTimestamp(mNow);
78 }
79 EXPECT_FALSE(tracker.needsMoreSamples(mNow));
80}
81
82TEST_F(VSyncPredictorTest, reportsSamplesNeededAfterExplicitRateChange) {
83 for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
84 tracker.addVsyncTimestamp(mNow += mPeriod);
85 }
86 EXPECT_FALSE(tracker.needsMoreSamples(mNow));
87
88 auto const changedPeriod = mPeriod * 2;
89 tracker.setPeriod(changedPeriod);
90 EXPECT_TRUE(tracker.needsMoreSamples(mNow));
91
92 for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
93 EXPECT_TRUE(tracker.needsMoreSamples(mNow += changedPeriod));
94 tracker.addVsyncTimestamp(mNow);
95 }
96 EXPECT_FALSE(tracker.needsMoreSamples(mNow));
97}
98
99TEST_F(VSyncPredictorTest, transitionsToModelledPointsAfterSynthetic) {
100 auto last = mNow;
101 auto const bias = 10;
102 for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
103 EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod));
104 mNow += mPeriod - bias;
105 last = mNow;
106 tracker.addVsyncTimestamp(mNow);
107 mNow += bias;
108 }
109
110 EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod - bias));
111 EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + mPeriod - bias));
112 EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 990), Eq(mNow + 2 * mPeriod - bias));
113}
114
115TEST_F(VSyncPredictorTest, uponNotifiedOfInaccuracyUsesSynthetic) {
116 auto const slightlyLessPeriod = mPeriod - 10;
117 auto const changedPeriod = mPeriod - 1;
118 for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
119 tracker.addVsyncTimestamp(mNow += slightlyLessPeriod);
120 }
121
122 EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + slightlyLessPeriod));
123 tracker.setPeriod(changedPeriod);
124 EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + changedPeriod));
125}
126
Kevin DuBois241d0ee2020-06-26 17:00:15 -0700127// b/159882858
128TEST_F(VSyncPredictorTest, updatesTimebaseForSyntheticAfterIdleTime) {
129 for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
130 EXPECT_TRUE(tracker.addVsyncTimestamp(mNow += mPeriod));
131 }
132
133 EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod));
134
135 auto const halfPeriod = mPeriod >> 2;
136 nsecs_t relativelyLongGapWithDrift = mPeriod * 100 + halfPeriod;
137
138 EXPECT_FALSE(tracker.addVsyncTimestamp(mNow += relativelyLongGapWithDrift));
139
140 tracker.resetModel();
141 EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod));
142}
143
144TEST_F(VSyncPredictorTest, uponBadVsyncWillSwitchToSyntheticWhileRecalibrating) {
145 auto const slightlyMorePeriod = mPeriod + 10;
146 for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
147 EXPECT_TRUE(tracker.addVsyncTimestamp(mNow += slightlyMorePeriod));
148 }
149
150 EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + slightlyMorePeriod));
151
152 auto const halfPeriod = mPeriod >> 2;
153 EXPECT_FALSE(tracker.addVsyncTimestamp(mNow += halfPeriod));
154
155 tracker.resetModel();
156 EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod));
157}
158
Kevin DuBois1678e2c2019-08-22 12:26:24 -0700159TEST_F(VSyncPredictorTest, adaptsToFenceTimelines_60hzHighVariance) {
160 // these are precomputed simulated 16.6s vsyncs with uniform distribution +/- 1.6ms error
161 std::vector<nsecs_t> const simulatedVsyncs{
162 15492949, 32325658, 49534984, 67496129, 84652891,
163 100332564, 117737004, 132125931, 149291099, 165199602,
164 };
165 auto constexpr idealPeriod = 16600000;
166 auto constexpr expectedPeriod = 16639242;
167 auto constexpr expectedIntercept = 1049341;
168
169 tracker.setPeriod(idealPeriod);
170 for (auto const& timestamp : simulatedVsyncs) {
171 tracker.addVsyncTimestamp(timestamp);
172 }
173 auto [slope, intercept] = tracker.getVSyncPredictionModel();
174 EXPECT_THAT(slope, IsCloseTo(expectedPeriod, mMaxRoundingError));
175 EXPECT_THAT(intercept, IsCloseTo(expectedIntercept, mMaxRoundingError));
176}
177
178TEST_F(VSyncPredictorTest, adaptsToFenceTimelines_90hzLowVariance) {
179 // these are precomputed simulated 11.1 vsyncs with uniform distribution +/- 1ms error
180 std::vector<nsecs_t> const simulatedVsyncs{
181 11167047, 22603464, 32538479, 44938134, 56321268,
182 66730346, 78062637, 88171429, 99707843, 111397621,
183 };
184 auto idealPeriod = 11110000;
185 auto expectedPeriod = 11089413;
186 auto expectedIntercept = 94421;
187
188 tracker.setPeriod(idealPeriod);
189 for (auto const& timestamp : simulatedVsyncs) {
190 tracker.addVsyncTimestamp(timestamp);
191 }
192 auto [slope, intercept] = tracker.getVSyncPredictionModel();
193 EXPECT_THAT(slope, IsCloseTo(expectedPeriod, mMaxRoundingError));
194 EXPECT_THAT(intercept, IsCloseTo(expectedIntercept, mMaxRoundingError));
195}
196
197TEST_F(VSyncPredictorTest, adaptsToFenceTimelinesDiscontinuous_22hzLowVariance) {
198 // these are 11.1s vsyncs with low variance, randomly computed, between -1 and 1ms
199 std::vector<nsecs_t> const simulatedVsyncs{
200 45259463, // 0
201 91511026, // 1
202 136307650, // 2
203 1864501714, // 40
204 1908641034, // 41
205 1955278544, // 42
206 4590180096, // 100
207 4681594994, // 102
208 5499224734, // 120
209 5591378272, // 122
210 };
211 auto idealPeriod = 45454545;
212 auto expectedPeriod = 45450152;
213 auto expectedIntercept = 469647;
214
215 tracker.setPeriod(idealPeriod);
216 for (auto const& timestamp : simulatedVsyncs) {
217 tracker.addVsyncTimestamp(timestamp);
218 }
219 auto [slope, intercept] = tracker.getVSyncPredictionModel();
220 EXPECT_THAT(slope, IsCloseTo(expectedPeriod, mMaxRoundingError));
221 EXPECT_THAT(intercept, IsCloseTo(expectedIntercept, mMaxRoundingError));
222}
223
224TEST_F(VSyncPredictorTest, againstOutliersDiscontinuous_500hzLowVariance) {
225 std::vector<nsecs_t> const simulatedVsyncs{
226 1992548, // 0
227 4078038, // 1
228 6165794, // 2
229 7958171, // 3
230 10193537, // 4
231 2401840200, // 1200
232 2403000000, // an outlier that should be excluded (1201 and a half)
233 2405803629, // 1202
234 2408028599, // 1203
235 2410121051, // 1204
236 };
237 auto idealPeriod = 2000000;
238 auto expectedPeriod = 1999892;
Kevin DuBois0049f8b2020-03-11 10:30:11 -0700239 auto expectedIntercept = 86342;
Kevin DuBois1678e2c2019-08-22 12:26:24 -0700240
241 tracker.setPeriod(idealPeriod);
242 for (auto const& timestamp : simulatedVsyncs) {
243 tracker.addVsyncTimestamp(timestamp);
244 }
245
246 auto [slope, intercept] = tracker.getVSyncPredictionModel();
247 EXPECT_THAT(slope, IsCloseTo(expectedPeriod, mMaxRoundingError));
248 EXPECT_THAT(intercept, IsCloseTo(expectedIntercept, mMaxRoundingError));
249}
250
251TEST_F(VSyncPredictorTest, handlesVsyncChange) {
252 auto const fastPeriod = 100;
253 auto const fastTimeBase = 100;
254 auto const slowPeriod = 400;
255 auto const slowTimeBase = 800;
256 auto const simulatedVsyncsFast =
257 generateVsyncTimestamps(kMinimumSamplesForPrediction, fastPeriod, fastTimeBase);
258 auto const simulatedVsyncsSlow =
259 generateVsyncTimestamps(kMinimumSamplesForPrediction, slowPeriod, slowTimeBase);
260
261 tracker.setPeriod(fastPeriod);
262 for (auto const& timestamp : simulatedVsyncsFast) {
263 tracker.addVsyncTimestamp(timestamp);
264 }
265
266 auto const mMaxRoundingError = 100;
267 auto [slope, intercept] = tracker.getVSyncPredictionModel();
268 EXPECT_THAT(slope, IsCloseTo(fastPeriod, mMaxRoundingError));
269 EXPECT_THAT(intercept, IsCloseTo(0, mMaxRoundingError));
270
271 tracker.setPeriod(slowPeriod);
272 for (auto const& timestamp : simulatedVsyncsSlow) {
273 tracker.addVsyncTimestamp(timestamp);
274 }
275 std::tie(slope, intercept) = tracker.getVSyncPredictionModel();
276 EXPECT_THAT(slope, IsCloseTo(slowPeriod, mMaxRoundingError));
277 EXPECT_THAT(intercept, IsCloseTo(0, mMaxRoundingError));
278}
279
280TEST_F(VSyncPredictorTest, willBeAccurateUsingPriorResultsForRate) {
281 auto const fastPeriod = 101000;
282 auto const fastTimeBase = fastPeriod - 500;
283 auto const fastPeriod2 = 99000;
284
285 auto const slowPeriod = 400000;
286 auto const slowTimeBase = 800000 - 201;
287 auto const simulatedVsyncsFast =
288 generateVsyncTimestamps(kMinimumSamplesForPrediction, fastPeriod, fastTimeBase);
289 auto const simulatedVsyncsSlow =
290 generateVsyncTimestamps(kMinimumSamplesForPrediction, slowPeriod, slowTimeBase);
291 auto const simulatedVsyncsFast2 =
292 generateVsyncTimestamps(kMinimumSamplesForPrediction, fastPeriod2, fastTimeBase);
293
294 auto idealPeriod = 100000;
295 tracker.setPeriod(idealPeriod);
296 for (auto const& timestamp : simulatedVsyncsFast) {
297 tracker.addVsyncTimestamp(timestamp);
298 }
299 auto [slope, intercept] = tracker.getVSyncPredictionModel();
300 EXPECT_THAT(slope, Eq(fastPeriod));
301 EXPECT_THAT(intercept, Eq(0));
302
303 tracker.setPeriod(slowPeriod);
304 for (auto const& timestamp : simulatedVsyncsSlow) {
305 tracker.addVsyncTimestamp(timestamp);
306 }
307
308 // we had a model for 100ns mPeriod before, use that until the new samples are
309 // sufficiently built up
310 tracker.setPeriod(idealPeriod);
311 std::tie(slope, intercept) = tracker.getVSyncPredictionModel();
312 EXPECT_THAT(slope, Eq(fastPeriod));
313 EXPECT_THAT(intercept, Eq(0));
314
315 for (auto const& timestamp : simulatedVsyncsFast2) {
316 tracker.addVsyncTimestamp(timestamp);
317 }
318 std::tie(slope, intercept) = tracker.getVSyncPredictionModel();
319 EXPECT_THAT(slope, Eq(fastPeriod2));
320 EXPECT_THAT(intercept, Eq(0));
321}
322
323TEST_F(VSyncPredictorTest, willBecomeInaccurateAfterA_longTimeWithNoSamples) {
324 auto const simulatedVsyncs = generateVsyncTimestamps(kMinimumSamplesForPrediction, mPeriod, 0);
325
326 for (auto const& timestamp : simulatedVsyncs) {
327 tracker.addVsyncTimestamp(timestamp);
328 }
329 auto const mNow = *simulatedVsyncs.rbegin();
330 EXPECT_FALSE(tracker.needsMoreSamples(mNow));
331
332 // TODO: would be better to decay this as a result of the variance of the samples
333 static auto constexpr aLongTimeOut = 1000000000;
334 EXPECT_TRUE(tracker.needsMoreSamples(mNow + aLongTimeOut));
335}
336
337TEST_F(VSyncPredictorTest, idealModelPredictionsBeforeRegressionModelIsBuilt) {
338 auto const simulatedVsyncs =
339 generateVsyncTimestamps(kMinimumSamplesForPrediction + 1, mPeriod, 0);
340 nsecs_t const mNow = 0;
341 EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mPeriod));
342
343 nsecs_t const aBitOfTime = 422;
344
345 for (auto i = 0; i < kMinimumSamplesForPrediction; i++) {
346 tracker.addVsyncTimestamp(simulatedVsyncs[i]);
347 EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(simulatedVsyncs[i] + aBitOfTime),
348 Eq(mPeriod + simulatedVsyncs[i]));
349 }
350
351 for (auto i = kMinimumSamplesForPrediction; i < simulatedVsyncs.size(); i++) {
352 tracker.addVsyncTimestamp(simulatedVsyncs[i]);
353 EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(simulatedVsyncs[i] + aBitOfTime),
354 Eq(mPeriod + simulatedVsyncs[i]));
355 }
356}
357
Kevin DuBois127a2d92019-12-04 13:52:52 -0800358// See b/145667109, and comment in prod code under test.
359TEST_F(VSyncPredictorTest, doesNotPredictBeforeTimePointWithHigherIntercept) {
360 std::vector<nsecs_t> const simulatedVsyncs{
361 158929578733000,
362 158929306806205, // oldest TS in ringbuffer
363 158929650879052,
364 158929661969209,
365 158929684198847,
366 158929695268171,
367 158929706370359,
368 };
369 auto const idealPeriod = 11111111;
Kevin DuBois0049f8b2020-03-11 10:30:11 -0700370 auto const expectedPeriod = 11113919;
371 auto const expectedIntercept = -1195945;
Kevin DuBois127a2d92019-12-04 13:52:52 -0800372
373 tracker.setPeriod(idealPeriod);
374 for (auto const& timestamp : simulatedVsyncs) {
375 tracker.addVsyncTimestamp(timestamp);
376 }
377
378 auto [slope, intercept] = tracker.getVSyncPredictionModel();
379 EXPECT_THAT(slope, IsCloseTo(expectedPeriod, mMaxRoundingError));
380 EXPECT_THAT(intercept, IsCloseTo(expectedIntercept, mMaxRoundingError));
381
382 // (timePoint - oldestTS) % expectedPeriod works out to be: 395334
383 // (timePoint - oldestTS) / expectedPeriod works out to be: 38.96
384 // so failure to account for the offset will floor the ordinal to 38, which was in the past.
385 auto const timePoint = 158929728723871;
386 auto const prediction = tracker.nextAnticipatedVSyncTimeFrom(timePoint);
387 EXPECT_THAT(prediction, Ge(timePoint));
388}
389
Kevin DuBois0049f8b2020-03-11 10:30:11 -0700390// See b/151146131
391TEST_F(VSyncPredictorTest, hasEnoughPrecision) {
392 VSyncPredictor tracker{mPeriod, 20, kMinimumSamplesForPrediction, kOutlierTolerancePercent};
393 std::vector<nsecs_t> const simulatedVsyncs{840873348817, 840890049444, 840906762675,
394 840923581635, 840940161584, 840956868096,
395 840973702473, 840990256277, 841007116851,
396 841023722530, 841040452167, 841057073002,
397 841073800920, 841090474360, 841107278632,
398 841123898634, 841140750875, 841157287127,
399 841591357014, 840856664232
400
401 };
402 auto const idealPeriod = 16666666;
403 auto const expectedPeriod = 16698426;
404 auto const expectedIntercept = 58055;
405
406 tracker.setPeriod(idealPeriod);
407 for (auto const& timestamp : simulatedVsyncs) {
408 tracker.addVsyncTimestamp(timestamp);
409 }
410
411 auto [slope, intercept] = tracker.getVSyncPredictionModel();
412 EXPECT_THAT(slope, IsCloseTo(expectedPeriod, mMaxRoundingError));
413 EXPECT_THAT(intercept, IsCloseTo(expectedIntercept, mMaxRoundingError));
414}
415
Kevin DuBoisc3e9e8e2020-01-07 09:06:52 -0800416TEST_F(VSyncPredictorTest, resetsWhenInstructed) {
417 auto const idealPeriod = 10000;
418 auto const realPeriod = 10500;
419 tracker.setPeriod(idealPeriod);
420 for (auto i = 0; i < kMinimumSamplesForPrediction; i++) {
421 tracker.addVsyncTimestamp(i * realPeriod);
422 }
423
424 EXPECT_THAT(std::get<0>(tracker.getVSyncPredictionModel()),
425 IsCloseTo(realPeriod, mMaxRoundingError));
426 tracker.resetModel();
427 EXPECT_THAT(std::get<0>(tracker.getVSyncPredictionModel()),
428 IsCloseTo(idealPeriod, mMaxRoundingError));
429}
430
Ady Abraham92fa2f42020-02-11 15:33:56 -0800431TEST_F(VSyncPredictorTest, slopeAlwaysValid) {
432 constexpr auto kNumVsyncs = 100;
433 auto invalidPeriod = mPeriod;
434 auto now = 0;
435 for (int i = 0; i < kNumVsyncs; i++) {
436 tracker.addVsyncTimestamp(now);
437 now += invalidPeriod;
438 invalidPeriod *= 0.9f;
439
440 auto [slope, intercept] = tracker.getVSyncPredictionModel();
441 EXPECT_THAT(slope, IsCloseTo(mPeriod, mPeriod * kOutlierTolerancePercent / 100.f));
442
443 // When VsyncPredictor returns the period it means that it doesn't know how to predict and
444 // it needs to get more samples
445 if (slope == mPeriod && intercept == 0) {
446 EXPECT_TRUE(tracker.needsMoreSamples(now));
447 }
448 }
449}
450
Kevin DuBois0049f8b2020-03-11 10:30:11 -0700451constexpr nsecs_t operator""_years(unsigned long long years) noexcept {
452 using namespace std::chrono_literals;
453 return years * 365 * 24 * 3600 *
454 std::chrono::duration_cast<std::chrono::nanoseconds>(1s).count();
455}
456TEST_F(VSyncPredictorTest, aPhoneThatHasBeenAroundAWhileCanStillComputePeriod) {
457 constexpr nsecs_t timeBase = 100_years;
458
459 for (auto i = 0; i < kHistorySize; i++) {
460 tracker.addVsyncTimestamp(timeBase + i * mPeriod);
461 }
462 auto [slope, intercept] = tracker.getVSyncPredictionModel();
463 EXPECT_THAT(slope, IsCloseTo(mPeriod, mMaxRoundingError));
464 EXPECT_THAT(intercept, Eq(0));
465}
466
Kevin DuBois1678e2c2019-08-22 12:26:24 -0700467} // namespace android::scheduler
Ady Abrahamb0dbdaa2020-01-06 16:19:42 -0800468
469// TODO(b/129481165): remove the #pragma below and fix conversion issues
470#pragma clang diagnostic pop // ignored "-Wconversion"