Skip to main content

k6

  • grafana/k6
    • AGPL-3.0, Golang
    • 现代化压测工具
    • 使用 JS 编写压测逻辑
    • 支持非常丰富的插件: JS 扩展、输出扩展
    • JS 引擎 dop251/goja
brew install k6
# https://github.com/grafana/k6/releases
curl -LO https://github.com/grafana/k6/releases/download/v0.45.0/k6-v0.45.0-linux-amd64.tar.gz
tar zxvf k6-v0.45.0-linux-amd64.tar.gz --strip-components 1
./k6

k6 run script.js
import { check, sleep } from 'k6';
import http from 'k6/http';

export const options = {
// 单个
vus: 10,
duration: '30s',
// or 多个场景
// stages: [
// { duration: '30s', target: 20 },
// { duration: '1m30s', target: 10 },
// { duration: '20s', target: 0 },
// ],
// 目标,SLO
thresholds: {
// Assert that 99% of requests finish within 3000ms.
http_req_duration: ['p(99) < 3000'],
},
};

export default function () {
const res = http.get('https://wener.me');
check(res, { 'status was 200': (r) => r.status == 200 });
sleep(1);
}
  • virtual users (VUs)
  • __ENV
// 1. init code - 每个 UV 调用 1 次

export function setup() {
// 2. setup code - 全局只会调用 1 次
return {};
}

// data 为 setup 返回结果
export default function (data) {
// 3. VU code - 每次迭代调用 1 次
}

// data 为 setup 返回结果
export function teardown(data) {
// 4. teardown code - 全局调用 1 次,如果 setup 失败则不调用
}

// 通过配置 scenarios.*.exec 来指定执行
export function webtest() {}
  • handleSummary
k6 run flagsfor
-u,--vus INT默认 1, 虚拟用户数
-d,--duration DURATION持续时间
-i,--iterations INT迭代次数
-s,--stage STAGE[duration]:[target]
--rps INT限制每秒请求数
--http-debug默认 headers, dump http request
--http-debug=full包含 body

context

  • instance
  • scenario
  • vu
import exec from 'k6/execution';

export default function () {
console.log(`Execution context

Instance info
-------------
Vus active: ${exec.instance.vusActive}
Iterations completed: ${exec.instance.iterationsCompleted}
Iterations interrupted: ${exec.instance.iterationsInterrupted}
Iterations completed: ${exec.instance.iterationsCompleted}
Iterations active: ${exec.instance.vusActive}
Initialized vus: ${exec.instance.vusInitialized}
Time passed from start of run(ms): ${exec.instance.currentTestRunDuration}

Scenario info
-------------
Name of the running scenario: ${exec.scenario.name}
Executor type: ${exec.scenario.executor}
Scenario start timestamp: ${exec.scenario.startTime}
Percenatage complete: ${exec.scenario.progress}
Iteration in instance: ${exec.scenario.iterationInInstance}
Iteration in test: ${exec.scenario.iterationInTest}

Test info
---------
All test options: ${exec.test.options}

VU info
-------
Iteration id: ${exec.vu.iterationInInstance}
Iteration in scenario: ${exec.vu.iterationInScenario}
VU ID in instance: ${exec.vu.idInInstance}
VU ID in test: ${exec.vu.idInTest}
VU tags: ${exec.vu.tags}`);
}

xk6

go install go.k6.io/xk6/cmd/xk6@latest

xk6 build --with github.com/grafana/xk6-output-timescaledb --with github.com/szkiba/xk6-dotenv@latest
./k6

# -o $K6_OUT
K6_OUT=timescaledb=postgresql://k6:k6@timescaledb:5432/k6 k6 run script.js

timescaledb

  • samples
  • thresholds
  • K6_TIMESCALEDB_PUSH_INTERVAL=1s
    • 2min 约 500 万 条记录、2G
    • 越长使用越多内存
k6 run --tag testid=SVR-$(date +%Y%m%d-%H%M)
CREATE TABLE IF NOT EXISTS samples (
ts timestamptz NOT NULL DEFAULT current_timestamp,
metric varchar(128) NOT NULL,
tags jsonb,
value real
);
CREATE TABLE IF NOT EXISTS thresholds (
id serial,
ts timestamptz NOT NULL DEFAULT current_timestamp,
metric varchar(128) NOT NULL,
tags jsonb,
threshold varchar(128) NOT NULL,
abort_on_fail boolean DEFAULT FALSE,
delay_abort_eval varchar(128),
last_failed boolean DEFAULT FALSE
);
SELECT create_hypertable('samples', 'ts');
CREATE INDEX IF NOT EXISTS idx_samples_ts ON samples (ts DESC);
CREATE INDEX IF NOT EXISTS idx_thresholds_ts ON thresholds (ts DESC);

-- 可以考虑
create index samples_ts_tags_testid on samples using btree (ts, ((tags ->> 'testid'::text)));

SQL

Setup

sudo apk add go
sudo chown $USER mkdir -p /data

mkdir -p /data/bin
go env -w GOPROXY=https://goproxy.cn,direct

go install go.k6.io/xk6/cmd/xk6@latest
GOBIN=/data/bin go install go.k6.io/xk6/cmd/xk6@latest

export PATH="/data/bin:$PATH"

# CGO_ENABLED=1 for SQLite
# --output /data/bin
xk6 build \
--with github.com/grafana/xk6-output-timescaledb \
--with github.com/szkiba/xk6-dotenv \
--with github.com/szkiba/xk6-dashboard \
--with github.com/avitalique/xk6-file \
--with github.com/szkiba/xk6-top \
--with github.com/grafana/xk6-sql

mv k6 /data/bin/
mkdir -p /data/k6
cd /data/k6

Target RPS

export const options = {
scenarios: {
constant_request_rate: {
executor: 'constant-arrival-rate',
rate: 1,
timeUnit: '1s',
duration: '1m',
preAllocatedVUs: 20,
maxVUs: 100,
},
stageOne: {
executor: 'constant-arrival-rate',
duration: '5m',
rate: 1000000, // 1mil QPS for 5 min
timeUnit: '1s',
maxVUs: 12500, // Some number with headroom where I try to judge, given some hypothetical SUT latency, how I can reach 1mil QPS?
},
stageTwo: {
executor: 'constant-arrival-rate',
duration: '5m',
startTime: '5m',
rate: 10000000, // 10mil QPS for 5 min
timeUnit: '1s',
maxVUs: 125000, // Ditto
},
// ...
},
};

gracefulStop