1// Copyright (C) 2024 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import {defer} from '../base/deferred'; 16import {raf} from '../core/raf_scheduler'; 17import {AppImpl} from '../core/app_impl'; 18 19/** 20 * This class is exposed by index.ts as window.waitForPerfettoIdle() and is used 21 * by tests, to detect when we reach quiescence. 22 */ 23 24const IDLE_HYSTERESIS_MS = 100; 25const TIMEOUT_MS = 30_000; 26 27export class IdleDetector { 28 private promise = defer<void>(); 29 private deadline = performance.now() + TIMEOUT_MS; 30 private idleSince?: number; 31 private idleHysteresisMs = IDLE_HYSTERESIS_MS; 32 33 waitForPerfettoIdle(idleHysteresisMs = IDLE_HYSTERESIS_MS): Promise<void> { 34 this.idleSince = undefined; 35 this.idleHysteresisMs = idleHysteresisMs; 36 this.scheduleNextTask(); 37 return this.promise; 38 } 39 40 private onIdleCallback() { 41 const now = performance.now(); 42 if (now > this.deadline) { 43 this.promise.reject( 44 `Didn't reach idle within ${TIMEOUT_MS} ms, giving up` + 45 ` ${this.idleIndicators()}`, 46 ); 47 return; 48 } 49 if (this.idleIndicators().every((x) => x)) { 50 this.idleSince = this.idleSince ?? now; 51 const idleDur = now - this.idleSince; 52 if (idleDur >= this.idleHysteresisMs) { 53 // We have been idle for more than the threshold, success. 54 this.promise.resolve(); 55 return; 56 } 57 // We are idle, but not for long enough. keep waiting 58 this.scheduleNextTask(); 59 return; 60 } 61 // Not idle, reset and repeat. 62 this.idleSince = undefined; 63 this.scheduleNextTask(); 64 } 65 66 private scheduleNextTask() { 67 requestIdleCallback(() => this.onIdleCallback()); 68 } 69 70 private idleIndicators() { 71 const reqsPending = AppImpl.instance.trace?.engine.numRequestsPending ?? 0; 72 return [ 73 reqsPending === 0, 74 !raf.hasPendingRedraws, 75 !document.getAnimations().some((a) => a.playState === 'running'), 76 document.querySelector('.progress.progress-anim') == null, 77 document.querySelector('.omnibox.message-mode') == null, 78 ]; 79 } 80} 81