requestAnimationFrameで端末負荷を測定する

背景

最近ブラウザから端末負荷を測定したい場面に出くわしました。 弊社ではwebrtcを扱っていて、ライブ中の端末負荷の変化を測定したかったのですが、残念ながらブラウザAPIは用意されていません。

そこでrequestAnimationFrameapiを使用して画面の描画回数を測定することで、間接的に端末負荷の指標とすることにしました。

Window.requestAnimationFrame()

requestAnimationFrameは本来はアニメーションを実装するために使用するAPIです。

Window.requestAnimationFrame() - Web API | MDN

ブラウザにアニメーションを行いたいことを知らせ、指定した関数を呼び出して次の再描画の前にアニメーションを更新することを要求します。

ブラウザが画面を描画するたびにコールされ、そのタイミングで任意の関数を実行したい場合に使用するのが一般的かと思います。

今回は、ブラウザが画面を描画するたびにコールされることを利用して、描画1回あたりに要した時間を計測しています。 1回あたりに要した時間が分かれば、画面描画のfpsも計算することができます。 つまり、端末負荷が上がって画面がカクカクする状態になればfpsも下がることを利用して、端末負荷の指標としましました。

正常時の基準とべきfpsは60ですが、注意も必要です。

このコールバックの回数は、たいてい毎秒 60 回ですが、一般的に多くのブラウザーでは W3C の勧告に従って、ディスプレイのリフレッシュレートに合わせて行われます。

特にゲーミング用のディスプレイなどはリフレッシュレートが高く設定されている場合もあり、必ずしも60を基準にできるとは限りません。

そもそももっと直接計測する方法はないのか?

google meetはどうやって端末負荷を計測しているのか?

googleの公式ではないので確かではないですが、chrome extensions apiを使用していると思われます。

javascript - How is Google Meet able to show CPU usage? - Stack Overflow

試しにgoogle meetをchrome以外のブラウザでアクセスしてみると、cpu使用率のグラフが表示されなくなっています。 かなり有力そうです。

残念ながらextensionsのapiをfrontendのコードから呼び出すことはできないので断念しました。 googleは外部に公開していない内部apiを通じて取得しているのではなかろうかと推測されます。

実装

requestAnimationFrame()のコールバック関数の中で、再起的にrequestAnimationFrame()を呼び出しています。 60回ごとに平均を計算しています。(60はあくまで目安です)

ポイントはInfinityです。理由が分かっていないのですが、時々lastCalledTimecurrentTimeが一致してしまうことがあり、fpsInfinityになってしまいます。 これを避けるために、Infinityを除外する処理を加えています。

const measureFps = () => {
  let lastCalledTime: number | undefined;
  let counter = 0;
  let fpsArray: number[] = [];

  const calcFps = () => {
    const currentTime = Date.now();

    if (lastCalledTime === undefined) {
      lastCalledTime = currentTime;
    } else {
      const delta = (currentTime - lastCalledTime) / 1000;
      lastCalledTime = currentTime;
      const fps = 1 / delta;

      if (counter >= 60) {
        const sum = fpsArray.reduce((a, b) => a + b);
        const average = sum / fpsArray.length;
        counter = 0;
        fpsArray = [fps];
        console.log({ average });

      } else if (fps !== Infinity) {
        fpsArray.push(fps);
        counter++;
      }
    }
    requestAnimationFrame(calcFps);
  };

  requestAnimationFrame(calcFps);
};

参考: Simple way to calculate the FPS when using requestAnimationFrame · GitHub

それほど多くもないですが、Infinityを弾いた分は誤差になるので根本解決はしたいなと思ってます...

それにしてもネイティブアプリはいいなぁ。読んで頂きありがとうございました!