k6でwebsocketを扱う

## k6って何？

オープンソースの負荷試験ツール。http 接続のみでなく、grpc や websocket にも対応している。
今回はこれを用いて WebSocket の負荷試験を行う。

## インストール

公式サイト参照。ubuntuであれば以下のコマンドを打ち込む。
https://k6.io/docs/getting-started/installation/

```
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update
sudo apt-get install k6
```



この通り実行すればヨシ。

途中で

```
gpg: failed to create temporary file '/root/.gnupg/...(略)' : No such file or director
```

と言われたので確認したら、/root/.gnupg がなかったので作成した。

```
sudo mkdir /root/.gnupg
```

## スクリプトの作成

以下の四つのステップによって成り立っている。

```
// 1. init

export function setup() {
// 2. step3を実行する前準備として一回だけ実行される
// ここの返り値をstep3の引数として渡すことができる
}

export default function (data) {
// 3. 試すテスト
}

export function teardown(data) {
// 4. 全テスト終了時に実行される
}
```


1. init で他のモジュールからインポート等の前準備を行う
2. setup でテスト前に必要な準備を行う。これはステップ3を行う前に一度だけ実行される
3. default functionとして実行するテストを描く
4. 全テスト終了時に実行される。

なおstep3以外は省略可能。default functionのみが実行される。


### httpの場合の例
http接続の場合、例えば以下のようなスクリプトとなる。
https://k6.io/docs/using-k6/checks/
```
import { check } from 'k6';
import http from 'k6/http';

export const options = {
  vus: 10,
  iterations: 10,
};

export default function () {
  const res = http.get('http://test.k6.io/');
  check(res, {
    'verify homepage text': (r) =>
      r.body.includes('Collection of simple web-pages suitable for load testing'),
  });
}
```
* optionsで指定したvus(バーチャルユーザー)が並列の実行数、iterationsが繰り返しの回数。これらの値はコマンド実行時の引数としても指定が可能。

https://k6.io/docs/using-k6/k6-options/reference/
* check()で得られたレスポンスを検証する。

https://k6.io/docs/javascript-api/k6/check/

* k6/httpパッケージからhttp.get()を実行する。

以下のようにして実行する。
```
k6 run script.js
```

出力値が得られる。
```
running (00m01.0s), 00/10 VUs, 10 complete and 0 interrupted iterations
default ✓ [================================] 10 VUs  00m01.0s/10m0s  10/10 shared iters 

     ✓ verify homepage text

     checks.........................: 100.00% ✓ 10        ✗ 0
     data_received..................: 173 kB  170 kB/s
     data_sent......................: 5.4 kB  5.3 kB/s
     http_req_blocked...............: avg=295.59ms min=189.1ms  med=294.41ms max=411.22ms p(90)=409.23ms p(95)=410.19ms
     http_req_connecting............: avg=182.76ms min=174.11ms med=184.7ms  max=190.56ms p(90)=187.51ms p(95)=189.21ms
     http_req_duration..............: avg=203.91ms min=180.19ms med=202.93ms max=227.96ms p(90)=222.8ms  p(95)=227ms
       { expected_response:true }...: avg=203.91ms min=180.19ms med=202.93ms max=227.96ms p(90)=222.8ms  p(95)=227ms
     http_req_failed................: 0.00%   ✓ 0         ✗ 20
     http_req_receiving.............: avg=1.12ms   min=0s       med=0s       max=16.71ms  p(90)=1.15ms   p(95)=3.18ms
     http_req_sending...............: avg=72.81µs  min=0s       med=0s       max=1.42ms 
  p(90)=18.2µs   p(95)=88.29µs
     http_req_tls_handshaking.......: avg=105.13ms min=0s       med=99.28ms  max=224.41ms p(90)=222.98ms p(95)=223.89ms
     http_req_waiting...............: avg=202.71ms min=179.18ms med=202.93ms max=227.96ms p(90)=220ms    p(95)=222.1ms
     http_reqs......................: 20      19.681399/s
     iteration_duration.............: avg=999.27ms min=980.82ms med=1s       max=1.01s  
  p(90)=1.01s    p(95)=1.01s
     iterations.....................: 10      9.8407/s
     vus............................: 7       min=7       max=7
     vus_max........................: 10      min=10      max=10
```

ここまでがhttpでの例。

## k6/ws

k6でwebsocketを扱う時は、k6/wsパッケージを用いる。

https://k6.io/docs/javascript-api/k6-ws/



http接続では```res = http.get('http://test.k6.io/');```としてレスポンスを渡したのに対して、ws接続では```res = ws.connect()```としてwsが接続してから切断するまでの処理を渡す。

https://k6.io/docs/javascript-api/k6-ws/connect

```
import ws from 'k6/ws';

export default function () {
  const url = 'ws://echo.websocket.org';
  const res = ws.connect(url, null, function (socket) {
    socket.on('open', function () {
      console.log('WebSocket connection established!');
      socket.close();
    });

    socket.on("message", function (message) {
      const msg = JSON.parse(message);
      console.log(msg);
    });
  });

  console.log(res);
  check(res, { 'Connected successfully': (r) => r && r.status === 101 });
}
```


なお、この際に```res```として得られる値は```body```の値を持たない。
```
console.log(res);
//{"url": "ws://echo.websocket.org","status":101,(略)"Connection":"upgrade","Upgrade":"WebSocket","Sec-Websocket-Accept":""},"body":"","error":""}
```
なのでbodyの値で検証といったことはできず、ステータスが101であるかを確認する程度となる。
```
check(res, { 'Connected successfully': (r) => r && r.status === 101 });
```

以下でメソッドの解説を行う。

### connect

https://k6.io/docs/javascript-api/k6-ws/connect

connectは
```
connect( url, params, callback )
```
と三つの引数を持つ。websocket接続の際にヘッダーやタグを設定する必要があれば、paramsに引き渡す。

https://k6.io/docs/javascript-api/k6-ws/params

なお、Connection, Upgrade, Sec-WebSocket-Keyといったwebsocket接続にそもそも必要なヘッダーはデフォルトで含まれているので入れる必要がない。

### Socket.send(data)
https://k6.io/docs/javascript-api/k6-ws/socket/socket-send

send()でメッセージの送信を行う。

```
  const res = ws.connect(url, null, function (socket) {
    socket.on("open", function open() {
      const message = { hoge: "fuga" };
      socket.send(JSON.stringify(message));
    });
  }
```

### Socket.setInterval(callback, interval)	

https://k6.io/docs/javascript-api/k6-ws/socket/socket-setinterval

setInterval()を使用する際はSocket.setInterval()を使用する。

定期的に送信を行うテストで使用できる。

```
  const res = ws.connect(url, params, function (socket) {
    socket.on('open', function open() {
      socket.setInterval(function timeout() {
        const message = { hoge: "fuga" };
        socket.send(JSON.stringify(message));
      }, 1000);
    });
```

### Socket.setTimeout(callback, period)
https://k6.io/docs/javascript-api/k6-ws/socket/socket-settimeout

setTimeoutを使用する際は```Socket.setTimeout```を使用する。
```
    socket.setTimeout(function () {
      socket.close();
    }, 2000);
```
### Socket.close()

https://k6.io/docs/javascript-api/k6-ws/socket/socket-close

```
socket.close();
```

接続を閉じる。これを実行すると```ws.connect()```の処理が終了し、値が返却される。

## 実行方法

以下のようにして実行する。
```
k6 run script.js
```

出力値が得られる。

```
     data_received........: 4.1 kB 4.7 kB/s
     data_sent............: 197 B  226 B/s
     iteration_duration...: avg=869.59ms min=869.59ms med=869.59ms max=869.59ms p(90)=869.59ms p(95)=869.59ms
     iterations...........: 1      1.148347/s
     ws_connecting........: avg=869.14ms min=869.14ms med=869.14ms max=869.14ms p(90)=869.14ms p(95)=869.14ms
     ws_sessions..........: 1      1.148347/s
     ws_msgs_received......: 1    2/s
     ws_msgs_sent..........: 1    2/s
```

* iteration_durationが実行にかかった時間
* iterationsが繰り返した回数
* ws_connectingがws接続に必要な時間
* ws_msgs_receivedが受信したメッセージの数
* ws_msgs_sentが送信したメッセージの数

なお、handleSummary関数を用いて出力値を変えることもできる。

https://k6.io/docs/results-visualization/end-of-test-summary/#handlesummary-callback

とりあえず全部出力してみる。
```
// 終了時に実行される
export function handleSummary(data) {
  return data
}
```