# 前言
使用 k6 來測試高併發
# 安裝
# Linux
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69 |
# Fedora/CentOS
sudo dnf install https://dl.k6.io/rpm/repo.rpm |
# MacOS
brew install k6 |
# 使用概述
default function 內的為 test 的內容
import http from 'k6/http';
import { sleep } from 'k6';
export default function () {
http.get('https://test.k6.io');
sleep(1);
}init code 每個 vu 只會執行一次, vu code 會一直執行, 假如 vus=10, iterations=100, 那 init code 只會跑 10 次, 而每個 vu 都會執行 script 10 次 (100/10)
// init code
export default function () {
// vu code
}可以使用 options 或 CLI, 效果一樣
import http from 'k6/http';
import { sleep } from 'k6';
export const options = {
vus: 10,
duration: '30s',
};
export default function () {
http.get('http://test.k6.io');
sleep(1);
}
// or CLI
k6 run --vus 10 --duration 30s script.js可在 test 中定義不同的階段,
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
stages: [
// 30 秒內達到 20 VUs
{ duration: '30s', target: 20 },
// 1m30s 降到 10 VUs
{ duration: '1m30s', target: 10 },
// 20s 降到 0 VUs
{ duration: '20s', target: 0 },
],
};
export default function () {
const res = http.get('https://httpbin.org/');
check(res, { 'status was 200': (r) => r.status == 200 });
sleep(1);
}output 預設會顯示多種資訊, 可從 CLI flag
summary-trend-stats
,summary-time-unit
控制輸出的資訊, 假如 CLI 中不出現 min, 那 output 就不會有 mink6 run --summary-trend-stats="min,avg,med,p(99),p(99.9),max,count" --summary-time-unit=ms script.js
可將 output 輸出指定格式到檔案, 或輸出到外部服務
k6 run \
--out json=test.json \
--out influxdb=http://localhost:8086/k6
# 使用詳解
# HTTP Requests
# GET request example
import http from 'k6/http'; |
# POST request example
import http from 'k6/http'; |
# 可用 method
method | 解釋 |
---|---|
batch() | 可併行發出複數 request |
del() | delete request |
get() | get request |
options() | options request |
patch() | patch reqeust |
post() | post request |
put() | put request |
request() | issue 任何 type 的 request |
# HTTP Request Tags
- k6 會自動給每個 request 上 tag, 可以利用 tag 來 filter 結果作分析
tags | 解釋 |
---|---|
expected_response | 預設 200~399 為 true, 可使用 setResponseCallback 修改 |
group | 如果 request 運行在某個 group 內, 那此 tag 為該 group name, 預設為 empty |
name | 預設為 url, 下面會講怎麼修改 |
method | 使用的 HTTP method |
scenario | 如果 request 運行在某個 group 內, 那此 tag 為該 group name, 預設為 default |
status | response status |
url | default 為 request url |
- 範例 response 如下:
{
"type": "Point",
"metric": "http_req_duration",
"data": {
"time": "2017-06-02T23:10:29.52444541+02:00",
"value": 586.831127,
"tags": {
"expected_response": "true",
"group": "",
"method": "GET",
"name": "http://test.k6.io",
"scenario": "default",
"status": "200",
"url": "http://test.k6.io"
}
}
}
# URL Grouping
預設, name tag 值為 url, 如下:
import http from 'k6/http';
for (let id = 1; id <= 100; id++) {
http.get(`http://example.com/posts/${id}`);
}
// tags.name=\"http://example.com/posts/1\",
// tags.name=\"http://example.com/posts/2\",然而, 可自定義, 不然一個 url 一個 name tag 會不好 filter
import http from 'k6/http';
for (let id = 1; id <= 100; id++) {
http.get(`http://example.com/posts/${id}`, {
tags: { name: 'PostsItemURL' },
});
}
// tags.name=\"PostsItemURL\",
// tags.name=\"PostsItemURL\",output 如下:
{
"type":"Point",
"metric":"http_req_duration",
"data": {
"time":"2017-06-02T23:10:29.52444541+02:00",
"value":586.831127,
"tags": {
"method":"GET",
"name":"PostsItemURL",
"status":"200",
"url":"http://example.com/1"
}
}
}
// and
{
"type":"Point",
"metric":"http_req_duration",
"data": {
"time":"2017-06-02T23:10:29.58582529+02:00",
"value":580.839273,
"tags": {
"method":"GET",
"name":"PostsItemURL",
"status":"200",
"url":"http://example.com/2"
}
}
}
# Metrics
# 內建 metrics
預設輸出如下
k6 run script.js
/\ |‾‾| /‾‾/ /‾‾/
/\ / \ | |/ / / /
/ \/ \ | ( / ‾‾\
/ \ | |\ \ | (‾) |
/ __________ \ |__| \__\ \_____/ .io
execution: local
script: http_get.js
output: -
scenarios: (100.00%) 1 scenario, 1 max VUs, 10m30s max duration (incl. graceful stop):
* default: 1 iterations for each of 1 VUs (maxDuration: 10m0s, gracefulStop: 30s)
running (00m03.8s), 0/1 VUs, 1 complete and 0 interrupted iterations
default ✓ [======================================] 1 VUs 00m03.8s/10m0s 1/1 iters, 1 per VU
data_received..................: 22 kB 5.7 kB/s
data_sent......................: 742 B 198 B/s
http_req_blocked...............: avg=1.05s min=1.05s med=1.05s max=1.05s p(90)=1.05s p(95)=1.05s
http_req_connecting............: avg=334.26ms min=334.26ms med=334.26ms max=334.26ms p(90)=334.26ms p(95)=334.26ms
http_req_duration..............: avg=2.7s min=2.7s med=2.7s max=2.7s p(90)=2.7s p(95)=2.7s
{ expected_response:true }...: avg=2.7s min=2.7s med=2.7s max=2.7s p(90)=2.7s p(95)=2.7s
http_req_failed................: 0.00% ✓ 0 ✗ 1
http_req_receiving.............: avg=112.41µs min=112.41µs med=112.41µs max=112.41µs p(90)=112.41µs p(95)=112.41µs
http_req_sending...............: avg=294.48µs min=294.48µs med=294.48µs max=294.48µs p(90)=294.48µs p(95)=294.48µs
http_req_tls_handshaking.......: avg=700.6ms min=700.6ms med=700.6ms max=700.6ms p(90)=700.6ms p(95)=700.6ms
http_req_waiting...............: avg=2.7s min=2.7s med=2.7s max=2.7s p(90)=2.7s p(95)=2.7s
http_reqs......................: 1 0.266167/s
iteration_duration.............: avg=3.75s min=3.75s med=3.75s max=3.75s p(90)=3.75s p(95)=3.75s
iterations.....................: 1 0.266167/s
vus............................: 1 min=1 max=1
vus_max........................: 1 min=1 max=1預設 metrics 敘述
METRIC NAME | TYPE | 敘述 |
---|---|---|
vus | Gauge | Current number of active virtual users |
vus_max | Gauge | Max possible number of virtual users (VU resources are pre-allocated, to ensure performance will not be affected when scaling up the load level) |
iterations | Counter | VUs 總共執行 default function 幾次, 不是單一 VU 執行的次數, 而是總數 |
iteration_duration | Trend | 執行完畢 default function 所耗費的時間 |
dropped_iterations | Counter | 無法開始的 iteration 數量, 可能因為缺少 VUs, 或缺少時間 |
data_received | Counter | 收到的檔案數量, example 來 track 特定的 request 的 data_received |
data_sent | Counter | 發出的檔案數量, 可參考 example 來 track 特定的 request 的 data_sent |
checks | Rate | The rate of successful checks. |
- HTTP-specific 預設 metrics 敘述
METRIC NAME | TYPE | 敘述 |
---|---|---|
http_reqs | Counter | k6 共產生多少 HTTP request, 總數 |
http_req_blocked | Trend | block 的時間, 即開始一個 request 前, 等待可用的 TCP connection slot 的時間 |
http_req_connection | Trend | 與 remote host 建立 TCP connection 所耗費的時間 |
http_req_tls_handshaking | Trend | 與 remote host tls handshaking 所耗費的時間 |
http_req_sending | Trend | 傳送 data 到 remote host 所耗費的時間 |
http_req_waiting | Trend | 從 remote host 收到第一筆資料所等待的時間 |
http_req_receiving | Trend | 從 remote host 接收資料, 從第一筆到完整結束所耗費的時間 |
http_req_duration | Trend | http_req_sending + http_req_waiting + http_req_receiving 的時間, 即發出一個 request 到完整接收 response 完畢所耗費的時間 |
http_req_failed | Rate | failed request rate |
# 取得指定 request 的 HTTP metric
- 可使用
res.timings
object, 有以下的 method 可以用:
METHOD NAME | 敘述 |
---|---|
blocked | 同 http_req_blocked |
connecting | 同 http_req_connection |
tls_handshaking | 同 http_req_tls_handshaking |
sending | 同 http_req_sending |
waiting | 同 http_req_waiting |
receiving | 同 http_req_receiving |
duration | 同 http_req_duration |
script 範例如下:
import http from 'k6/http';
export default function () {
const res = http.get('http://httpbin.org');
console.log('Response time was ' + String(res.timings.duration) + ' ms');
}輸出範例
k6 run script.js
INFO[0001] Response time was 337.962473 ms source=console
# Custom metrics
script example:
import http from 'k6/http';
import { Trend } from 'k6/metrics';
const myTrend = new Trend('waiting_time');
export default function () {
const r = http.get('https://httpbin.org');
myTrend.add(r.timings.waiting);
console.log(myTrend.name); // waiting_time
}output example:
k6 run script.js
...
INFO[0001] waiting_time source=console
...
iteration_duration.............: avg=1.15s min=1.15s med=1.15s max=1.15s p(90)=1.15s p(95)=1.15s
iterations.....................: 1 0.864973/s
waiting_time...................: avg=265.245396 min=265.245396 med=265.245396 max=265.245396 p(90)=265.245396 p(95)=265.245396
# Metric types
- 類型
METRIC TYPE | 敘述 |
---|---|
Counter | 會將值累計相加 |
Gauge | 存最小及最大值, 以及最後一個值 |
Rate | 計算 non-zero 的累計 percentage |
Trend | 計算累計值取得統計資料, min, max, average, percentiles |
counter script example
import { Counter } from 'k6/metrics';
const myCounter = new Counter('my_counter');
export default function () {
myCounter.add(1);
myCounter.add(2);
}counter 輸出 example
k6 run script.js
...
iteration_duration...: avg=16.48µs min=16.48µs med=16.48µs max=16.48µs p(90)=16.48µs p(95)=16.48µs
iterations...........: 1 1327.67919/s
my_counter...........: 3 3983.037571/sGauge script example
import { Gauge } from 'k6/metrics';
const myGauge = new Gauge('my_gauge');
export default function () {
myGauge.add(3);
myGauge.add(1);
myGauge.add(2);
}Gauge output example:
k6 run script.js
...
iteration_duration...: avg=21.74µs min=21.74µs med=21.74µs max=21.74µs p(90)=21.74µs p(95)=21.74µs
iterations...........: 1 1293.475322/s
my_gauge.............: 2 min=1 max=3Trend script example:
import { Trend } from 'k6/metrics';
const myTrend = new Trend('my_trend');
export default function () {
myTrend.add(1);
myTrend.add(2);
}Trend output example:
k6 run script.js
...
iteration_duration...: avg=20.78µs min=20.78µs med=20.78µs max=20.78µs p(90)=20.78µs p(95)=20.78µs
iterations...........: 1 1217.544821/s
my_trend.............: avg=1.5 min=1 med=1.5 max=2 p(90)=1.9 p(95)=1.95Rate script example:
import { Rate } from 'k6/metrics';
const myRate = new Rate('my_rate');
export default function () {
myRate.add(true);
myRate.add(false);
myRate.add(1);
myRate.add(0);
}Rate output example:
k6 run script.js
...
iteration_duration...: avg=22.12µs min=22.12µs med=22.12µs max=22.12µs p(90)=22.12µs p(95)=22.12µs
iterations...........: 1 1384.362792/s
my_rate..............: 50.00% ✓ 2 ✗ 2
# Checks
checks 跟 assertion 類似, 不同之處在於它不會停止, 而是會記下 check 結果, 並繼續執行
檢查 HTTP response code
import { check } from 'k6';
import http from 'k6/http';
export default function () {
const res = http.get('http://test.k6.io/');
check(res, {
'is status 200': (r) => r.status === 200,
});
}檢查 response body
import { check } from 'k6';
import http from 'k6/http';
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'),
});
}檢查 body size
import { check } from 'k6';
import http from 'k6/http';
export default function () {
const res = http.get('http://test.k6.io/');
check(res, {
'body size is 11,105 bytes': (r) => r.body.length == 11105,
});
}check output example:
k6 run script.js
...
✓ is status 200
...
checks.........................: 100.00% ✓ 1 ✗ 0
data_received..................: 11 kB 12 kB/s檢查多項
import { check } from 'k6';
import http from 'k6/http';
export default function () {
const res = http.get('http://test.k6.io/');
check(res, {
'is status 200': (r) => r.status === 200,
'body size is 11,105 bytes': (r) => r.body.length == 11105,
});
}output example
k6 run checks.js
...
✓ is status 200
✓ body size is 11,105 bytes
...
checks.........................: 100.00% ✓ 2 ✗ 0
data_received..................: 11 kB 20 kB/s
# Thresholds
若有任一 threshold 失敗, 則該 test 被視為 failed
threshold script example
import http from 'k6/http';
export const options = {
thresholds: {
http_req_failed: ['rate<0.01'], // http errors should be less than 1%
http_req_duration: ['p(95)<200'], // 95% of requests should be below 200ms
},
};
export default function () {
http.get('https://test-api.k6.io/public/crocodiles/1/');
}threshold output example, 若上面的兩個 thresholds 有任何一個失敗, 則 http_req_failed 會等於 1
✓ http_req_duration..............: avg=151.06ms min=151.06ms med=151.06ms max=151.06ms p(90)=151.06ms p(95)=151.06ms
{ expected_response:true }...: avg=151.06ms min=151.06ms med=151.06ms max=151.06ms p(90)=151.06ms p(95)=151.06ms
✓ http_req_failed................: 0.00% ✓ 0 ✗ 1example, 90% 的 request 必須在 400ms 內結束
import http from 'k6/http';
import { sleep } from 'k6';
export const options = {
thresholds: {
// 90% of requests must finish within 400ms.
http_req_duration: ['p(90) < 400'],
},
};
export default function () {
http.get('https://test-api.k6.io/public/crocodiles/1/');
sleep(1);
}example, error rate 必須在 1% 以下
import http from 'k6/http';
import { sleep } from 'k6';
export const options = {
thresholds: {
// During the whole test execution, the error rate must be lower than 1%.
// `http_req_failed` metric is available since v0.31.0
http_req_failed: ['rate<0.01'],
},
};
export default function () {
http.get('https://test-api.k6.io/public/crocodiles/1/');
sleep(1);
}example, 90% request 必須在 400ms 完成, 95% 在 800ms 完成, 99.9% 在 2s 內完成
import http from 'k6/http';
import { sleep } from 'k6';
export const options = {
thresholds: {
// 90% of requests must finish within 400ms, 95% within 800, and 99.9% within 2s.
http_req_duration: ['p(90) < 400', 'p(95) < 800', 'p(99.9) < 2000'],
},
};
export default function () {
const res1 = http.get('https://test-api.k6.io/public/crocodiles/1/');
sleep(1);
}example, 指定 group 內的 request 必須平均在 200ms 內完成
import http from 'k6/http';
import { group, sleep } from 'k6';
export const options = {
thresholds: {
'group_duration{group:::individualRequests}': ['avg < 200'],
'group_duration{group:::batchRequests}': ['avg < 200'],
},
vus: 1,
duration: '10s',
};
export default function () {
group('individualRequests', function () {
http.get('https://test-api.k6.io/public/crocodiles/1/');
http.get('https://test-api.k6.io/public/crocodiles/2/');
http.get('https://test-api.k6.io/public/crocodiles/3/');
});
group('batchRequests', function () {
http.batch([
['GET', `https://test-api.k6.io/public/crocodiles/1/`],
['GET', `https://test-api.k6.io/public/crocodiles/2/`],
['GET', `https://test-api.k6.io/public/crocodiles/3/`],
]);
});
sleep(1);
}Syntax example
import http from 'k6/http';
import { Trend, Rate, Counter, Gauge } from 'k6/metrics';
import { sleep } from 'k6';
// 這邊定義各個 custom metric 的名稱, 以及其 type
export const TrendRTT = new Trend('RTT');
export const RateContentOK = new Rate('Content OK');
export const GaugeContentSize = new Gauge('ContentSize');
export const CounterErrors = new Counter('Errors');
// 這裡用上面定義的 custom metric 來定義 thresholds
export const options = {
thresholds: {
// 99% request response time 需低於 300 ms, 70% < 250 ms, avg < 200 ms, median < 150 ms, min < 100 ms
'RTT': ['p(99)<300', 'p(70)<250', 'avg<200', 'med<150', 'min<100'],
// content 正確率必須 > 95%
'Content OK': ['rate>0.95'],
// returned content < 4000 bytes
'ContentSize': ['value<4000'],
// content can't have been bad more than 99 times
'Errors': ['count<100'],
},
};
// 在 default function 中, 實際對 custom metric 做操作
export default function () {
const res = http.get('https://test-api.k6.io/public/crocodiles/1/');
const contentOK = res.json('name') === 'Bert';
TrendRTT.add(res.timings.duration);
RateContentOK.add(contentOK);
GaugeContentSize.add(res.body.length);
CounterErrors.add(!contentOK);
sleep(1);
}Thresholads on tags
import http from 'k6/http';
import { sleep } from 'k6';
import { Rate } from 'k6/metrics';
export const options = {
thresholds: {
// 使用 tag 定義 thresholds
'http_req_duration{type:API}': ['p(95)<500'], // threshold on API requests only
'http_req_duration{type:staticContent}': ['p(95)<200'], // threshold on static content only
},
};
export default function () {
// 定義 tags
const res1 = http.get('https://test-api.k6.io/public/crocodiles/1/', {
tags: { type: 'API' },
});
// 定義 tags
const res2 = http.get('https://test-api.k6.io/public/crocodiles/2/', {
tags: { type: 'API' },
});
const responses = http.batch([
// 定義 tags
['GET', 'https://test-api.k6.io/static/favicon.ico', null, { tags: { type: 'staticContent' } }],
// 定義 tags
[
'GET',
'https://test-api.k6.io/static/css/site.css',
null,
{ tags: { type: 'staticContent' } },
],
]);
sleep(1);
}當 threshold 達標, 停止 test, abortOnFail 為 boolean, 代表達標時是否 abort, delayAbortEval 代表達標後, delay 多少時間才 abort
export const options = {
thresholds: {
metric_name: [
{
threshold: 'p(99) < 10', // string
abortOnFail: true, // boolean
delayAbortEval: '10s', // string
/*...*/
},
],
},
};使用 checks 搭配 thresholds 來 fail test, 若 checks 沒通過, 則該 request 視為 failed request
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
vus: 50,
duration: '10s',
thresholds: {
// the rate of successful checks should be higher than 90%
checks: ['rate>0.9'],
},
};
export default function () {
const res = http.get('http://httpbin.org');
check(res, {
'status is 500': (r) => r.status == 500,
});
sleep(1);
}給 checks 定義 tag, 在用 tag 來定義 thresholds 也行
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
vus: 50,
duration: '10s',
// 使用 tag 定義的 thresholds
thresholds: {
'checks{myTag:hola}': ['rate>0.9'],
},
};
export default function () {
let res;
res = http.get('http://httpbin.org');
check(res, {
'status is 500': (r) => r.status == 500,
});
res = http.get('http://httpbin.org');
// 給 check 上一個 tag
check(
res,
{
'status is 200': (r) => r.status == 200,
},
{ myTag: 'hola' }
);
sleep(1);
}
留言