• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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