5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

requestAnimationFrameで端末負荷を測定する

Last updated at Posted at 2021-10-11

背景

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

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

Window.requestAnimationFrame()

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

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

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

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

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

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

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

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

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

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

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

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

実装

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

ポイントはInfinityです。理由が分かっていないのですが、時々lastCalledTimecurrentTimeが一致してしまうことがあり、fpsがInfinityになってしまいます。
これを避けるために、Infinityを除外する処理を加えています。
** requestAnimationFrameのcallbackの引数に渡ってくる、highResTimestampを使うことでこの問題は解消できました。
(一応Infinityのチェックを入れてますが、なくても良いかもしれません)

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

  const calcFps = (highResTimestamp: DOMHighResTimeStamp) => {
    if (lastCalledTime === undefined) {
      lastCalledTime = highResTimestamp;
    } else {
      const delta = (highResTimestamp - lastCalledTime) / 1000;
      lastCalledTime = highResTimestamp;
      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);
};

参考: https://gist.github.com/WA9ACE/d51659371a345a9327bd

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

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

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?