NeuStar VirtualUser Script 覚書

米国の Neustar 社が提供しているサービスの一つで、Webサイトに対する負荷試験が行える。

- 公式
	- [Neustar | Home](https://home.wpm.neustar.biz/)
- API Document
	- [biz\.neustar\.wpm\.api \(Neustar Web Performance Management Scripting API\)](http://docs.wpm.neustar.biz/testscript-api/index.html)
- スクリプト概要
	- [Neustar Website Load Testing のサンプルスクリプト\(VirtualUser\)生成 \- vague memory](http://htnosm.hatenablog.com/entry/2018/02/05/090000)

以下、VirtualUserでの試験実行でよく使うサンプルなど。

# 基本(GET)

単発でGET実行。POST等の他メソッドも概ね同じように利用可能。

```
var c = test.openHttpClient();

// Set Parameters
c.setCheckValidSSL(false);
c.setConnectionTimeout(30000);
c.setDNSCacheTimeout(0);
c.setEnableSNI(true);
c.setFollowRedirects(true);
c.setRequestTimeout(60000);
c.setRetryCount(0);
c.setSocketOperationTimeout(30000);
// Set Parameters

test.beginTransaction();
test.beginStep("Check Website");

c.get("https://google.com/");

test.endStep();
test.endTransaction();
```

## HttpClient 設定変更

HttpClientに設定可能な値

| Class | AllowValues | Deafult | Note |
| --- | --- | --- | --- | --- |
| setCheckValidSSL | boolean | false | SSL証明書検証 |
| setConnectionTimeout | int | 30000 | 接続タイムアウト、単位：ミリ秒 |
| setDNSCacheTimeout | int | ？ | DNSキャッシュTTL、-1は永久、0は無効 |
| setEnableSNI | boolean | true | SNI有効化、'handshake alert: unrecognized_name:'が返される場合は false を指定 |
| setFollowRedirects | boolearn | true | リダイレクト追跡 |
| setRequestTimeout | int | -1 | リクエスト全体のタイムアウト(リトライも含む) |
| setRetryCount | int | 0 | リトライ回数 |
| setSocketOperationTimeout | int | 30000 | ソケットタイムアウト、単位：ミリ秒 |

- set〜Timeout を全て -1(無効) にすると永続化される
	- Operation timed out が別に設定おり、75秒程で切断は発生する模様

## レスポンスのチェック

返却されるステータスコードの検証

- リダイレクトは無効化する必要がある

```
var c = test.openHttpClient();
test.beginTransaction();
test.beginStep("Check Website");

c.setFollowRedirects(false);
c.get("http://google.com/xxx",302);
c.get("https://www.google.co.jp/",200);

test.endStep();
test.endTransaction();
```

Statusコードが異なる場合は失敗の扱いとなり、後続処理は行われない

```
Error Message: Expected status code of 302 but saw 404
```

## レスポンス情報取得

レスポンスから各種情報を取得する

```
var c = test.openHttpClient();
test.beginTransaction();
test.beginStep("Check Website");

c.setFollowRedirects(false);
var res = c.get("https://www.google.co.jp/",200);
var info = res.getInfo();
test.log('Info BlockedTime = ' + info.getBlockedTime());
test.log('Info Browser = ' + info.getBrowser());
test.log('Info ConnectTime = ' + info.getConnectTime());
test.log('Info DnsLookupTime = ' + info.getDnsLookupTime());
test.log('Info End = ' + info.getEnd());
test.log('Info ErrorMessage = ' + info.getErrorMessage());
test.log('Info Host = ' + info.getHost());
test.log('Info Method = ' + info.getMethod());
test.log('Info Path = ' + info.getPath());
test.log('Info PostBody = ' + info.getPostBody());
test.log('Info PostParams = ' + info.getPostParams());
test.log('Info Protocol = ' + info.getProtocol());
test.log('Info QueryString = ' + info.getQueryString());
test.log('Info ReasonPhrase = ' + info.getReasonPhrase());
test.log('Info ReceiveTime = ' + info.getReceiveTime());
test.log('Info RedirectUrl = ' + info.getRedirectUrl());
test.log('Info RequestHeaders = ' + info.getRequestHeaders());
test.log('Info ResolvedIpAddress = ' + info.getResolvedIpAddress());
test.log('Info ResponseHeaders = ' + info.getResponseHeaders());
test.log('Info SendTime = ' + info.getSendTime());
test.log('Info SslHandshakeTime = ' + info.getSslHandshakeTime());
test.log('Info Start = ' + info.getStart());
test.log('Info StatusCode = ' + info.getStatusCode());
test.log('Info TimeActive = ' + info.getTimeActive());
test.log('Info TimeToFirstByte = ' + info.getTimeToFirstByte());
test.log('Info Url = ' + info.getUrl());
test.log('Info WebSocketMessages = ' + info.getWebSocketMessages());
test.log('Body = ' + res.getBody());
test.log('CharSet = ' + res.getCharSet());
test.log('ContentType = ' + res.getContentType());
test.log('ErrorMessage = ' + res.getErrorMessage());
test.log('Url = ' + res.getUrl());
test.log('StatusCode = ' + res.getStatusCode());
test.log('StatusText = ' + res.getStatusText());
res.getAllHeaders().forEach(function(header){
  test.log('Header = ' + header.name + " : " + header.value);
});

test.endStep();
test.endTransaction();
```

- 取得例

```
=====================================================
Script Log
=====================================================
Info BlockedTime = 0
Info Browser = null
Info ConnectTime = 0
Info DnsLookupTime = 2016
Info End = Sun Feb 25 19:46:37 JST 2018
Info ErrorMessage = null
Info Host = www.google.co.jp
Info Method = GET
Info Path = /
Info PostBody = null
Info PostParams = {}
Info Protocol = https
Info QueryString = null
Info ReasonPhrase = OK
Info ReceiveTime = 580
Info RedirectUrl = null
Info RequestHeaders = {User-Agent=[Ljava.lang.String;@6fe643b9, Connection=[Ljava.lang.String;@25a1d233, Host=[Ljava.lang.String;@43afa9bc, Accept-Encoding=[Ljava.lang.String;@5494c28b}
Info ResolvedIpAddress = 172.217.26.35
Info ResponseHeaders = {X-Frame-Options=[Ljava.lang.String;@932a82b, Transfer-Encoding=[Ljava.lang.String;@2229c374, Cache-Control=[Ljava.lang.String;@1cf57cc4, Server=[Ljava.lang.String;@6e07754b, Alt-Svc=[Ljava.lang.String;@7268eb50, Content-Encoding=[Ljava.lang.String;@1eb6a40a, Set-Cookie=[Ljava.lang.String;@2992e844, Expires=[Ljava.lang.String;@7cea6d10, P3P=[Ljava.lang.String;@496d2bb6, X-XSS-Protection=[Ljava.lang.String;@62504065, Date=[Ljava.lang.String;@5c3e44a3, Content-Type=[Ljava.lang.String;@731320d9}
Info SendTime = 3
Info SslHandshakeTime = 0
Info Start = Sun Feb 25 19:46:34 JST 2018
Info StatusCode = 200
Info TimeActive = 2711
Info TimeToFirstByte = 112
Info Url = https://www.google.co.jp/
Info WebSocketMessages = []
Body = <!doctype html>
・・・略
</html>
CharSet = Shift_JIS
ContentType = text/html; charset=Shift_JIS
ErrorMessage = null
Url = https://www.google.co.jp/
StatusCode = 200
StatusText = OK
Header = Date : Sun, 25 Feb 2018 10:46:22 GMT
Header = Expires : -1
・・・略
Header = Transfer-Encoding : chunked
```

### Cookie 取得

ログイン情報をPOSTし、Cookieを取得、使用する例

```
var c = test.openHttpClient();
test.beginTransaction();
test.beginStep("Check Website");

var req = c.newPost("https://test.example.com/login.html");
req.addRequestParameters({
  'username': 'hoge',
  'password': 'fuga',
});
res = req.execute();
cookie = res.getHeader('Set-Cookie');
test.log('cookie : ' + cookie);

var req = c.newGet("https://test.example.com/");
req.addRequestHeaders({
'Cookie': cookie,
});
var res = web.execute(req);

test.endStep();
test.endTransaction();
```

## HttpClient 動作変更

### remapHost

ドメイン名を置き換える。 /etc/hosts のような使い方が可能。

```
var c = test.openHttpClient();
c.remapHost(sourceDomain, domain);
```

### BASIC認証

```
var domain = "test.example.com";
var user = "hoge";
var pass = "fuga";

c.autoBasicAuthorization(domain, user, pass);
```

### UserAgent 書き換え

規程のUserAgent `"WPM HttpClient/1.0"` を任意の文字列に書き換える。

```
var c = test.openHttpClient();
c.addRequestInterceptor(function(req) {
	req.removeHeaders("User-Agent");
	req.addHeader("User-Agent", "iPhone; WPM HttpClient/1.0");
});
```


# 応用(GET)

- WebBot
	- HTMLを解析し、ページ内リンクに沿ってダウンロードを実施する
- HttpClient.newGet
	- リクエスト実行前にGET処理を書き換える

## ページ内リンクダウンロード

HTMLを解析し、リンクを追従。
Javascript、CSSは解析対象外。  
JMeterで言う所の HTTPリクエストの<B>繰り返しダウンロードする</B>。


### 解析・ダウンロード無し

```
var c = test.openHttpClient();

test.beginTransaction();
test.beginStep("Check Website");

var req = c.newGet("https://www.google.co.jp/");
req.execute();

test.endStep();
test.endTransaction();
```

- 指定URLのみが対象

```
Total Objects: 1

	12143b	200	1466ms	https://www.google.co.jp/
```

### 解析・ダウンロード有り


```
var c = test.openHttpClient();
var web = require('webbot');

test.beginTransaction();
test.beginStep("Check Website");

var req = c.newGet("https://www.google.co.jp/");
var res = web.execute(req);

test.endStep();
test.endTransaction();
```

- HTML内のURLまで対象となる

```
Total Objects: 2

	12635b	200	846ms	https://www.google.co.jp/
	22151b	200	134ms	https://www.google.co.jp/logos/doodles/2018/girls-day-2018-5202534598705152.2-l.png
```

## 特定のドメイン・URLを無視する

- 通常(リスト無し)の場合の返却値

```
	492231b	200	1533ms	https://www.youtube.com/
	5878b	200	47ms	https://www.youtube.com/yts/jsbin/scheduler-vfl3G47Co/scheduler.js
	・・・
	13661b	200	485ms	https://i.ytimg.com/vi/7glUp6bDU-k/hqdefault.jpg?sqp=-oaymwEWCMQBEG5IWvKriqkDCQgBFQAAiEIYAQ==&rs=AOn4CLBTAJOcDbo4HIbx4ZfiuRgdqx8Reg
	・・・
	2353b	200	289ms	https://yt3.ggpht.com/ES6oTwtm9iPPjKbkLSbFLpaRdbRObia30kmFKZdcCP1gBasaKEdyVYxrz6zkBC36m-_eYI3pRleRoMEMR3k=s88-nd-c-c0xffffffff-rj-k-no
	43b	200	30ms	https://www.youtube.com/yts/img/pixel-vfl3z5WfW.gif
	・・・
```

### blacklistRequests

正規表現に一致するHTTPリクエストには固定のステータスコードを返す。

```
var c = test.openHttpClient();
var web = require('webbot');

test.beginTransaction();
test.beginStep("Check Website");

c.blacklistRequests("https://i\\.ytimg\\.com/.*", 204);
c.blacklistRequests(".*yt3.ggpht.com.*", 204);

var req = c.newGet("https://www.youtube.com/");
var res = web.execute(req);

test.endStep();
test.endTransaction();
```

- blacklistRequests での返却値

```
	514856b	200	1462ms	https://www.youtube.com/
	5878b	200	17ms	https://www.youtube.com/yts/jsbin/scheduler-vfl3G47Co/scheduler.js
	・・・
	0b	204	0ms	https://i.ytimg.com/vi/FYVn2ne7hJc/hqdefault.jpg?sqp=-oaymwEWCMQBEG5IWvKriqkDCQgBFQAAiEIYAQ==&rs=AOn4CLD35p4cHO4FZrVP8dzLpSQEn2PwoQ
	・・・
	0b	204	0ms	https://i.ytimg.com/vi/xyaFPwzWS20/hqdefault.jpg?sqp=-oaymwEWCMQBEG5IWvKriqkDCQgBFQAAiEIYAQ==&rs=AOn4CLAWZwsWBeYp_VTp6-0NJxyAVNyO5A
	43b	200	13ms	https://www.youtube.com/yts/img/pixel-vfl3z5WfW.gif
	・・・
```

### whitelistRequests

正規表現に一致するリストに無いHTTPリクエストには固定のステータスコードを返す。配列指定可。

```
var c = test.openHttpClient();
var web = require('webbot');

test.beginTransaction();
test.beginStep("Check Website");

c.whitelistRequests([
  ".*www.youtube.com.*",
], 204);

var req = c.newGet("https://www.youtube.com/");
var res = web.execute(req);
//req.execute();

test.endStep();
test.endTransaction();
```

- whitelistRequests での返却値

```
	517492b	200	1374ms	https://www.youtube.com/
	5878b	200	24ms	https://www.youtube.com/yts/jsbin/scheduler-vfl3G47Co/scheduler.js
	・・・
	0b	204	0ms	https://i.ytimg.com/vi/FYVn2ne7hJc/hqdefault.jpg?sqp=-oaymwEWCMQBEG5IWvKriqkDCQgBFQAAiEIYAQ==&rs=AOn4CLD35p4cHO4FZrVP8dzLpSQEn2PwoQ
	・・・
	0b	204	0ms	https://i.ytimg.com/vi/x0zgWgzy8Ow/hqdefault.jpg?sqp=-oaymwEWCMQBEG5IWvKriqkDCQgBFQAAiEIYAQ==&rs=AOn4CLAhkXbjeKJ0s8iXTN4upWXXa5CGYA
	43b	200	14ms	https://www.youtube.com/yts/img/pixel-vfl3z5WfW.gif
	・・・
```

# その他

## csv からの値取得

行番号指定かランダム読み込み。

`test.getCSV(name)` の name 指定はリテラルである必要がある。<b>変数では展開されずエラー</b>となる。
(LocalValidator では成功するが、NeuStar上だとエラー)

```
Error: script-exception: "Problem fetching CSV file"
```

- 例 data.csv

```
Name,Age,MailAddress
Yamada,22,t-yamada@example.com
Aikawa,28,s-aikawa@example.com
Endo,21,endo@@example.com
```

### 行番号読み込み

```
// readCsv
function readCsv(csv){
  var array = [];
  for (var i = 0; i < csv.size(); i++) {
    var row = csv.get(i);
    array[i] = row;

    var name = row.get("Name");
    var age = row.get("Age");
    var mail = row.get("MailAddress");
    test.log('Name: ' + name + ' / Age: ' + age + ' / Mail: ' + mail);
  }
  return array;
}
var csv  = test.getCSV("data.csv");
var data = readCsv(csv);
test.log(data);
```

### ランダム読み込み

```
var csv  = test.getCSV("data.csv");
var csv_row = csv.random();
var name = csv_row.get("Name");
var age = csv_row.get("Age");
var mail = csv_row.get("MailAddress");
test.log('Name: ' + name + ' / Age: ' + age + ' / Mail: ');
```

## 一時停止

Step 間に一時停止時間を設定。ミリ秒単位。30秒制限有り。(Local Validator だと制限はない模様)

```
var c = test.openHttpClient();
var web = require('webbot');

test.beginTransaction();
test.beginStep("Check Website1");
var req = c.newGet("https://www.youtube.com/");
req.execute();
test.endStep();

test.pause(30000);

test.beginStep("Check Website2");
var req = c.newGet("https://google.com/");
req.execute();
test.endStep();

test.endTransaction();
```

## assert (異常終了)

異常終了させたい場合に使用。

```
test.assertTrue(false, "Assert!!!");

```

- 出力例

```
WARN mm/dd hh:mm:ss b.n.w.a.a.Webmetric~ - ERROR: assertTrue() failed! Assert!!! at line 8
```

- 出力例(validation.txt)

```
***** ERROR DETECTED *****
Error Message: assertTrue() failed! Assert!!!
Error Line Num: 8
***** ERROR DETECTED *****
```

APIリファレンスを見ると他にも似たような関数( <b>fail、 assertFalse</b> )があるが、いずれも見つからないエラーとなる。処理は停止するが美しくはない。

- 出力例(validation.txt)

```
***** ERROR DETECTED *****
Error Message: TypeError: Cannot find function fail in object biz.neustar.wpm.api.Test.
Error Line Num: 9
***** ERROR DETECTED *****
```

## Validate 時間制限

NeuStar 上での Validate は Script 全体の処理時間が 60秒を超過するとエラーとなる。

```
Script took too long and reached timeout limit.
```

制限緩和は行えないため、Local Validator で Validate を行った後、Bypass Validation を有効にしてスキップさせる。