3
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.

WebRTCでも負荷試験がしたい!

Posted at

Puppeteer + Fargate で負荷テストするお話

今回は他のWebRTC負荷テストツールやサービスについては扱いません。
それなりにコストがかかるのと、やっぱりブラウザでの動作するか検証したいからです。

「テストするのでこのURL開いてください。ひとり10タブで」

一般的なWebサイトと違い、WebRTCや動画配信サービスで負荷テストをするのはなかなか大変です。
人数が多い会社では、上記のようにアナウンスして協力して貰えば100接続くらいはなんとかなります。
ただし、社内LANに負荷をかけても大丈夫か、あるいはいろんな経路で試したい、など痒いところに手が届きません。

AWSBatch + Fargateでお手軽に300接続したい!

経路はアマゾンからになってしまいますが、Batch + Fargateを使えばお手軽にヘッドレスChromeで接続することができます。
EC2インスタンスだと100台制限の緩和申請など必要ですが、Fargateだともっとたくさん起動できます。

dockerファイルが書きたい!

puppeteer docker で検索すると大体同じです。

Dockerfile
FROM alpine:edge

RUN apk update

# japanese font
RUN apk add --no-cache curl fontconfig font-noto-cjk \
  && fc-cache -fv

# Installs latest Chromium (76) package.
RUN apk add --no-cache \
      chromium \
      nss \
      freetype \
      freetype-dev \
      harfbuzz \
      ca-certificates \
      ttf-freefont \
      nodejs \
      yarn 

# timezone
RUN apk add --update --no-cache tzdata && \
    cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
    echo "Asia/Tokyo" > /etc/timezone && \
    apk del tzdata

# Tell Puppeteer to skip installing Chrome. We'll be using the installed package.
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true

WORKDIR /app
COPY ./package.json /app/package.json
COPY ./yarn.lock /app/yarn.lock
RUN yarn


COPY main.js /app

CMD ["yarn", "start"]

あとは ECRにpushしておきます。

Puppeteerで接続したい!

ちなみに、AWSBatchでは引数を渡すことはできず、他プロセスと識別するにはインデックスを使います。

const uid = process.env.AWS_BATCH_JOB_ARRAY_INDEX
  ? "test_"+process.env.AWS_BATCH_JOB_ARRAY_INDEX
  : "local";

puppeteerで接続する処理は基本的なものと同じです。

const puppeteer = require('puppeteer'),
  WebinarURL = "動作確認したいページのURL"

... なんか初期化とか

(async() => {
try{
  const browser = await puppeteer.launch({
    executablePath: "/usr/bin/chromium-browser",
    headless: true,
    ignoreHTTPSErrors: true,
    defaultViewport: null,
    dumpio: false,
    args: ['--disable-setuid-sandbox', '--no-sandbox'] // ←これ大事
  });

  const page = await browser.newPage();

  // 必要ならログインとかする
  // WebRTCのページにアクセスする
  await page.goto(WebinarURL);
  await sleep(500);
  ...

  while(true) {
    // 動画が動いているか確認
    // ログ出力
    sleep(60); // 一分毎にチェックする
  }
  await browser.close();
} catch (err) {
  // catchしておかないとプロセスが終了しない
  console.log(err);
}
})();

package.jsonはとてもシンプル

package.json
{
  "name": "client_sama_sama",
  "version": "1.0.0",
  "main": "main.js",
  "dependencies": {
    "puppeteer": "5.5.0",
    "aws-sdk": "2.824.0"
  },
  "scripts": {
    "start": "node main.js"
  },
  "license": "UNLICENSED"
}

音声が出ているか確認したい!

無理でした。。
いや、音声データが流れていることを確認すればそれでも良いのですが
本当に音が出てるの? というのを確認したかったのです。
ところが、headless chromeだとオーディオキャプチャの実装がないとかで難しそうでした。

      /* デスクトップオーディオを取得して、音がととしるかチェックする。だが、DOMException: Not supported
       * issue : https://github.com/puppeteer/puppeteer/issues/4404
       * やり方 : https://paul.kinlan.me/ja/screen-recorderrecording-microphone-and-the-desktop-audio-at-the-same-time/
      if (!window.analyser) {
        const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
        // ノード作成
        const desktopStream = await navigator.mediaDevices.getDisplayMedia({ video:true, audio: true });
        const audioSource = audioCtx.createMediaStreamSource(desktopStream);
        const desktopGain = context.createGain();
        desktopGain.gain.value = 0.7;
        const analyser = audioCtx.createAnalyser();
        // 接続
        audioSource.connect(desktopGain).connect(analyser).connect(audioCtx.destination);
        window.analyser = analyser;
      }
      const analyser = window.analyser;
      // データから音声レベルを取得
      const frequencies = new Uint8Array(analyser.frequencyBinCount);
      analyser.getByteFrequencyData(frequencies);
      const audioLevel = frequencies.reduce(function(previous, current) {
        return previous + current;
      }) / analyser.frequencyBinCount;

      // analyser.frequencyBinCount
      */

動画が止まっていないか確認したい!

キャンパスにコピーして、0(真っ暗でないか)、前回と同じでないか確認します。
最初の一回、固まった後の復帰、などなど考えると意外と面倒ですがそこは割愛します。

    // ブラウザ上でjavascriptを実行する
    const {ログ出力したい戻り値をここで受け取る...} = await page.evaluate(async() => {
      // ここはブラウザ上

      // 0出ないピクセルがあればなにか写ってる
      function isCanvasSome(ctx) {
        return canvas.getContext("2d")
          .getImageData(0, 0, canvas.width, canvas.height).data
          .some(channel => channel !== 0);
      }
      const video = document.getElementById("remotevideo-Camera");
      if (!video || video.videoWidth === 0) {
        return ...;
      }
      if (!window.tempCanvas) {
        window.tempCanvas = document.createElement("canvas");
      }
      const canvas = window.tempCanvas;
      canvas.width = video.videoWidth;
      canvas.height = video.videoHeight;
      const videoCtx = canvas.getContext("2d").drawImage(video, 0, 0, video.videoWidth, video.videoWidth);

       // 音声チェックしたかったけど断念

      return {videoWidthとかgetStats()した戻り値とか}
    }); 

    // 戻り値をログ出力する

CloudWatchでグラフ化がしたい!

cloudwatchに投げてやればいい感じでグラフにできます。
60秒単位より細かく撮ろうとするとコストが上がるので、とりあえず60秒で良いと思います。

  • 動画の横幅 width
  • ビットレート
  • 動画が動いているかフラグ
  • その他 RTCPeerConnection.getStats() で取れるパケロスとかジッターとかお好きなもの

すみません。
あまりディメンジョンやら概念が分かっておらず、
とりあえず以下の設定で、動いているか? とwidthがインスタンス毎にグラフ化されます。
大体、配信サーバーを再起動したり帯域上限に引っかかるとwidthが安定しないのが見てとれます。

    const cw = new AWS.CloudWatch({apiVersion: '2010-08-01'});

   cw.putMetricData({
      MetricData: [{
        MetricName: 'VideoOkCount',
        Dimensions: [
          {
            Name: 'name',
            Value: uid,
          },
        ],
        Timestamp: new Date(),
        Unit: 'Count',
        Value: (videoOK && isMoving ? 1 : 0),
      },{
        MetricName: 'VideoWidth',
        Dimensions: [
          {
            Name: 'name',
            Value: uid,
          },
        ],
        Timestamp: new Date(),
        Unit: 'Count',
        Value: videoWidth,
      }],
      Namespace: 'ClinetSamaSama/LoadTest'
    }, (err, data) => { if (err) { console.log(err); } });

AWSの環境構築がしたい!

長くなるので要点だけ記載します。

  • API用のアクセスキーを作る
  • Batch用のロールを作る
  • VPCを作る。
  • ステージング環境のアクセス制限がかかっている場合は、VPCエンドポイントで繋ぐなりピアで繋ぐなりセキュリティグループ当てるなりして疎通させる
  • Batchのコンピューティング環境を作る
    • vCPU : インスタンスの上限数にする (1インスタンス1vCPUで良い)
  • ジョブ定義を作る
    • ロールを作る AmazonECSTaskExecutionRolePolicyをアタッチ
    • パブリックIPを有効にしておく
    • イメージ : pushしたdocker
    • mem : 2GBは欲しいところ
  • ジョブキューを作る
  • タスク数をお好きなものにして実行する
  • CloudWatchにアクセスし、グラフを見て思いを巡らせる。
  • 注意 : タスクが終了していても課金され続けることがあるので、コンピューティング環境も削除すると安心です。(使い方を間違っているだけかもしれません)

独り言

2,3年のブランクを経て再びWebRTCをやる機会をいただきました。
手が掛かる子ほど可愛いといいますか、やっぱWebRTCいいよなーと思います。
Unix Network Programing と TCP Illustrated と マスタリングTCP/IP RTP編 を読みつつ (全て絶版。。)
そろそろRustでなんか書こうかなー
音声や映像をリアルタイムにサーバー側でAIと繋ぎ込む需要ありそうだなー
クラウドネイティブでスケールするミドルウェアも書きたいなー
と思う今日この頃です。

3
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
3
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?