Edited at

NeuStar VirtualUser Script 覚書

More than 1 year has passed since last update.

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

以下、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リクエストの繰り返しダウンロードする


解析・ダウンロード無し

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 指定はリテラルである必要がある。変数では展開されずエラーとなる。

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


  • 出力例(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 を有効にしてスキップさせる。