Repository navigation
v0.28.0
- Branching and data-driven scenarios in YAML, enough to port k6 scripts
likeif (Math.random() < 0.15) like(post)one to one:- step-level
if:and its equivalent aliaswhen:—==,!=,<,
<=,>,>=,&&,||over interpolated values and${…}tokens;
skipped steps record nothing; declaring both is a parse error; std/random@v1— explicit seeded integer/float steps with numeric output
for dynamic sleeps, array indexes and probability rolls;${{ __vu__ }}/${{ __iter__ }}(k6's__VU/__ITER);- paths index arrays and read into JSON bodies:
${{ feed.body.posts.0.id }}; std/pick@v1— a random or per-VU element from an array or a JSON file
(parsed once per process), with signedindex_offsetapplied before
modulo for exact 1-based-to-0-based index mapping;std/sleep@v1expands tokens (ms: "${rand(3000,7000)}") and rejects
non-numeric strings instead of silently sleeping 1s.
- step-level
- JSON steps and paths: new
std/read-json@v1reads and parses a JSON file
into the step output (process-wide cache keyed by(mtime, size), so
iterations past the first skip both the disk read and the parse), and a new
queryparameter onstd/read-json@v1,std/file-read@v1, and
std/file-write@v1takes a jq-style path subset (.posts[0].id≡
posts.0.id; a?suffix on a segment turns a miss intonull).
file-readextracts the subvalue from JSON content,file-writedoes a
read-modify-write that setscontentat the path (creating intermediate
objects/arrays like jq; an array index equal to the length appends).
${{ ... }}interpolation accepts the same paths, including bracket
indexing, and a path that continues past a string leaf — an HTTPbody—
parses it as JSON on demand:${{ feed.body.posts[0].id }}reads straight
into a response without a separate extract step. The same reader is also
a built-in library,@std/json@v1:${json.read("users.json", ".users[0].token")}inside generator tokens, sharing the step's
parsed-document cache and its fail-closedallow_file_actionsgate (a
tolerant?miss resolves to an empty string in tokens; a mandatory miss
is an expansion error naming the segment).@std/json@v1also exports
pick(file[, index])— a uniformly random (seeded-run deterministic) or
index-addressed element of a JSON array file. Like@std/random@v1, the
library is opt-in: it resolves only when declared inlibraries:. std/http@v1pool: dedicated— one HTTP client per VU, created on the
VU's first request and held until the VU exits. The default shard pools
cap idle connections at 64 per host per shard, so past
~shards × 64VUs a VU's keep-alive connection is evicted between its
requests (connect churn, TIME_WAIT buildup); dedicated mode removes the
ceiling for "N VUs = N standing connections" scenarios. Think time
(std/sleep@v1) does not close the connection, clients are dropped at VU
exit (fixed, staged, and arrival-rate runs alike), and the mode composes
withsource_ips(client key:(vu_id, source_ip)).- Multi-source-IP HTTP load (
std/http@v1source_ips): a step can bind
outgoing connections to a list of local source addresses — bare IPs or
CIDR prefixes (source_ips: ["10.0.0.2", "10.0.0.16/28", "fd00::/120"],
expansion capped at 4096 addresses). The ephemeral source-port space is
per (source IP, dst IP, dst port), so multiple source addresses multiply
the ~64k-concurrent-connections-per-endpoint budget of one machine. VUi
is pinned toips[(i-1) % n](stable across iterations, keep-alive stays
warm); clients are built lazily per (shard, address) pair, the first
request logs how many addresses are active, andperfscale lintvalidates
the list before the run. - Typed run inputs (issue #11): config files declare an
inputs:block
(numberwithmin/max,string,boolwith 0/1/false/true coercion,
GitHub-stylechoicewithoptions:), steps interpolate
${{ inputs.<name> }}everywhere${{ ... }}works, and the CLI overrides
values at launch:-i users=10 -i env=prod, the comma form
-i users=10,env=prod(\,escapes a literal comma), and-e file.env
env files. Precedence:-i> env file >default:; unknown keys, type
mismatches, out-of-range values and missing required inputs fail before
the run starts, andperfscale lintprints the effective resolved values. - Config extensions (issue #10): WASM libraries built against
perfscale:library@0.3.0may exportconfig-schema, claiming one
top-level config key (mylibrary: { ... }) with a JSON Schema for its
value. The engine validates the block at lint and run start and hands it
to the guest via the run settings'extensionsobject. Unknown top-level
keys stay a hard error (the typo guard moved from schema parse to this
validation), and two libraries claiming one key is a hard error. Older
(0.1/0.2) components keep loading unchanged. std/pubsub@v1: newflushparameter (message|none, default
message— unchanged).flush: messageflushes and awaits the broker ack
after publishing, sopubsub_publish_msis the full round trip;
flush: noneis fire-and-forget (samples measure the enqueue only) — the
xk6-nats posture, for like-for-like warm throughput comparisons. Pairs
withscope: vu; withscope: exchangeone unsampled flush still runs
before the connection drops so queued messages are not lost.- WASM reporters compile once, not twice: a reporter component
(use: wasm:./reporter.wasm) used to pay a full Cranelift compilation at
both validate and delivery — and again on everylint. The compiled
component is now shared within a run, and a write-through burn cache
(<cache>/reporters/<sha256>.cwasm, samePFSBURN1format and
invalidation rules as libraries) makes repeatlint/rundeserialize
native code instead of recompiling.perfscale installprewarms WASM
reporters alongside libraries (the explicit warm-cache step), and the
lint/validate path writes the artifact in the background — a cold lint no
longer pays the serialize+write on its critical path. - Built-in
htmlreporter (std/report/html@v1,--report html:report.html):
a single self-contained dark-themed page with the run identity, threshold
verdicts, a sortable per-step table with samples sparklines, and metric
families — all CSS/JS inlined, opens offline, archives as a CI artifact.
What's Changed
- feat(core): std/read-json@v1, jq-style query paths, @std/json@v1 library by @vitalics in #17
- feat(core): conditional steps, std/pick and std/random by @vitalics in #16
Full Changelog: v0.27.0...v0.28.0