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'; 18import {taskTracker} from './task_tracker'; 19 20/** 21 * This class is exposed by index.ts as window.waitForPerfettoIdle() and is used 22 * by tests, to detect when we reach quiescence. 23 */ 24 25const IDLE_HYSTERESIS_MS = 100; 26const TIMEOUT_MS = 60_000; 27 28export class IdleDetector { 29 private promise = defer<void>(); 30 private deadline = performance.now() + TIMEOUT_MS; 31 private idleSince?: number; 32 private idleHysteresisMs = IDLE_HYSTERESIS_MS; 33 34 waitForPerfettoIdle(idleHysteresisMs = IDLE_HYSTERESIS_MS): Promise<void> { 35 this.idleSince = undefined; 36 this.idleHysteresisMs = idleHysteresisMs; 37 this.scheduleNextTask(); 38 return this.promise; 39 } 40 41 private onIdleCallback() { 42 const now = performance.now(); 43 if (now > this.deadline) { 44 this.promise.reject( 45 `Didn't reach idle within ${TIMEOUT_MS} ms, giving up` + 46 ` ${this.idleIndicators()}`, 47 ); 48 return; 49 } 50 if (this.idleIndicators().every((x) => x)) { 51 this.idleSince = this.idleSince ?? now; 52 const idleDur = now - this.idleSince; 53 if (idleDur >= this.idleHysteresisMs) { 54 // We have been idle for more than the threshold, success. 55 this.promise.resolve(); 56 return; 57 } 58 // We are idle, but not for long enough. keep waiting 59 this.scheduleNextTask(); 60 return; 61 } 62 // Not idle, reset and repeat. 63 this.idleSince = undefined; 64 this.scheduleNextTask(); 65 } 66 67 private scheduleNextTask() { 68 requestIdleCallback(() => this.onIdleCallback()); 69 } 70 71 private idleIndicators() { 72 const reqsPending = AppImpl.instance.trace?.engine.numRequestsPending ?? 0; 73 return [ 74 !AppImpl.instance.isLoadingTrace, 75 reqsPending === 0, 76 !raf.hasPendingRedraws, 77 !taskTracker.hasPendingTasks(), 78 !document.getAnimations().some((a) => a.playState === 'running'), 79 document.querySelector('.progress.progress-anim') == null, 80 document.querySelector('.omnibox.message-mode') == null, 81 ]; 82 } 83} 84