Skip to content

v0.28.0

Choose a tag to compare

@github-actions github-actions released this 26 Oct 16:28
· 118 commits to main since this release
v0.28.0
637217c
  • Branching and data-driven scenarios in YAML, enough to port k6 scripts
    like if (Math.random() < 0.15) like(post) one to one:
    • step-level if: and its equivalent alias when: — ==, !=, <,
      <=, >, >=, &&, || 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 signed index_offset applied before
      modulo for exact 1-based-to-0-based index mapping;
    • std/sleep@v1 expands tokens (ms: "${rand(3000,7000)}") and rejects
      non-numeric strings instead of silently sleeping 1s.
  • JSON steps and paths: new std/read-json@v1 reads 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
    query parameter on std/read-json@v1, std/file-read@v1, and
    std/file-write@v1 takes a jq-style path subset (.posts[0].id ≡
    posts.0.id; a ? suffix on a segment turns a miss into null).
    file-read extracts the subvalue from JSON content, file-write does a
    read-modify-write that sets content at 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 HTTP body —
    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-closed allow_file_actions gate (a
    tolerant ? miss resolves to an empty string in tokens; a mandatory miss
    is an expansion error naming the segment). @std/json@v1 also 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 in libraries:.
  • std/http@v1 pool: 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 × 64 VUs 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
    with source_ips (client key: (vu_id, source_ip)).
  • Multi-source-IP HTTP load (std/http@v1 source_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. VU i
    is pinned to ips[(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, and perfscale lint validates
    the list before the run.
  • Typed run inputs (issue #11): config files declare an inputs: block
    (number with min/max, string, bool with 0/1/false/true coercion,
    GitHub-style choice with options:), 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, and perfscale lint prints the effective resolved values.
  • Config extensions (issue #10): WASM libraries built against
    perfscale:library@0.3.0 may export config-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' extensions object. 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: new flush parameter (message | none, default
    message — unchanged). flush: message flushes and awaits the broker ack
    after publishing, so pubsub_publish_ms is the full round trip;
    flush: none is fire-and-forget (samples measure the enqueue only) — the
    xk6-nats posture, for like-for-like warm throughput comparisons. Pairs
    with scope: vu; with scope: exchange one 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 every lint. The compiled
    component is now shared within a run, and a write-through burn cache
    (<cache>/reporters/<sha256>.cwasm, same PFSBURN1 format and
    invalidation rules as libraries) makes repeat lint/run deserialize
    native code instead of recompiling. perfscale install prewarms 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 html reporter (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