k6 - 使用筆記

# 前言

使用 k6 來測試高併發

# 安裝

# Linux

sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb 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

# Fedora/CentOS

sudo dnf install https://dl.k6.io/rpm/repo.rpm
sudo dnf install k6

# 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 就不會有 min

    k6 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';

export default function () {
http.get('http://test.k6.io');
}
# POST request example
import http from 'k6/http';

export default function () {
const url = 'http://test.k6.io/login';
const payload = JSON.stringify({
email: 'aaa',
password: 'bbb',
});

const params = {
headers: {
'Content-Type': 'application/json',
},
};

http.post(url, payload, params);
}
# 可用 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/s
  • Gauge 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=3
  • Trend 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.95
  • Rate 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 ✗ 1
  • example, 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);
    }
Laravel - 高併發 Laravel - Packages - Socialite

留言

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×