Repository navigation
Add extension usage telemetry - #6126
Merged
Merged
Conversation
inancgumus
requested review from
ankur22,
mstoykov and
szkiba
and removed request for
a team and
mstoykov
July 3, 2026 19:02
inancgumus
temporarily deployed
to
azure-trusted-signing
July 3, 2026 19:08 — with
GitHub Actions
Inactive
inancgumus
temporarily deployed
to
azure-trusted-signing
July 3, 2026 19:10 — with
GitHub Actions
Inactive
szkiba
previously approved these changes
Jul 7, 2026
ankur22
reviewed
Jul 28, 2026
ankur22
left a comment
Contributor
There was a problem hiding this comment.
Really nice work!
A few questions inline, none blocking. Two small things here:
- The description says a slow endpoint "never delays or fails a command", but
k6 xsends its report before exiting, so a hanging endpoint can add up to 3 seconds to the exit. Probably fine, just worth softening the wording. - The usage-collection docs will need an update —
k6 xnow sends a report, and the run report has a newextensionsfield. Is that tracked somewhere?
Contributor
Author
|
Great review, @ankur22! Yes, Task #19 tracks the docs update, which I'll do once this PR merges 🤝 I've also reworded the PR description. For the review requests, I've created a stacked follow-up PR, which we can merge after this one. |
The usage report endpoint was a hardcoded const posting to stats.grafana.org, so no test could observe the anonymous usage report. Reading the endpoint from K6_USAGE_REPORT_URL (defaulting to the production endpoint) lets integration tests point it at a local server and assert the report is sent.
The usage report tracked only built-in modules and dropped every k6/x/ import, so extension adoption was invisible. Resolved Go extensions are now recorded in a dedicated extensions bucket at the cached resolution site, identified by registry membership rather than the k6/x/ name, and reported with their Go module path, version, and kind, de-duplicated per module and kind. Only extensions whose module path appears in the public registry catalog are reported, so private or unlisted extensions never leak. The catalog is read through the same cache file, TTL, and K6_PROVISION_CATALOG_URL override the k6 x subcommand path uses, fetched inside the bounded report send off the run's hot path, and an unreachable catalog fail-closed reports nothing. When nothing catalogued was used, the extensions key is omitted rather than sent as an empty array, consistent with how modules behaves.
The usage report must not list the same extension multiple times when many VUs import it. This adds a --vus 5 --iterations 5 acceptance row asserting a module imported across VUs yields exactly one js entry, locking in the per-(module,kind) de-duplication done at report assembly.
The usage report must list every imported extension, not collapse distinct modules into one slot. This guards run's reporting so that two extensions in separate Go packages, which resolve to distinct module paths, each surface as their own js entry and survive de-duplication.
An empty or fully filtered extensions result omits the key entirely, consistent with how modules behaves, so key presence always means at least one catalogued extension was used. Nothing pinned that. This adds a row asserting a run that uses no extension produces no extensions key rather than an empty array.
The report filter keys on Go module path, but nothing pinned that a private fork reusing a public import name resolves to its own path and is dropped. A guard row now imports an in-tree fork registered under a catalogued import name while the catalog advertises only the real extension's path; the fork's distinct module path keeps it out of the report, so the row fails if matching ever regresses to import names.
catalogModulePaths returns nothing when the catalog cannot be fetched and resolveExtensions drops the raw bucket before consulting it, so an unreachable catalog with no cache reports no extensions instead of leaking the unfiltered list. Nothing pinned that. This adds a row importing a used extension against an unreachable catalog, asserting the report carries no extensions and the run still exits 0.
The usage report gained a catalog fetch and extension enrichment. This guards that the existing K6_NO_USAGE_REPORT opt-out still gates all of it: with the opt-out set, neither the report endpoint nor the catalog server is contacted, so no new opt-out is needed and none of the added work runs ahead of the gate.
Output extensions chosen via --out were tracked only in the built-in outputs list, so catalogued xk6 output extensions never appeared in the usage report. A selected output that is a member of the output registry is now recorded as a resolved registry entry in the shared extensions bucket, so the report filters it by module path like every other surface. Built-in outputs stay out of the bucket.
The output extension bucket shared the same catalog filter as JS imports, but nothing pinned that behavior. This adds a test row proving a used output extension absent from the catalog is dropped from the usage report, so a future change that routes outputs around the filter fails loudly.
Extension usage recording keys the extensions bucket on output-registry membership, so built-in outputs like json were never reported as extensions. Without a test, a future change could broaden recording to all outputs and start leaking built-ins. This guards that json stays listed under outputs and never produces an output-kind extension entry.
Baked-in subcommands invoked via `k6 x` sent no usage telemetry, so the extension registry had no signal about their use. The root execute seam now reports once after the command runs, using cobra's ExecuteC to recover the command that ran and its error, so a registered subcommand extension is reported whether it succeeds or fails, through the shared usage-report transport. It carries only the run-independent identifying fields and the catalogued extension entry, bounded and non-fatal so it never fails or delays the command, and the command's own hook is left untouched.
createSubcommandReport keeps a subcommand's entry only when its module path appears in the public catalog, matching the run path's filter. Nothing pinned that. This adds a row running a baked-in subcommand whose module path the catalog omits, asserting the report carries no entry for it.
Before, nothing guarded against a usage report firing for a subcommand name the binary does not provide. This adds a guard: running k6 x with an unregistered name against an unreachable catalog builds no command and sends no report, so an early or stray report call would now be caught.
The subcommand report rides the shared bounded, non-fatal send, so a slow or unreachable endpoint cannot stall or fail k6 x. Nothing pinned that. This adds a row proving the send is abandoned within the bounded run-report timeout, the command still exits 0 with no surfaced error, and the failure surfaces only as a debug-level log.
Nothing exercised the K6_NO_USAGE_REPORT gate on the k6 x report path, so a regression could re-enable telemetry unnoticed. This adds a table row that runs k6 x under the opt-out and asserts neither the report endpoint nor the catalog is consulted.
inancgumus
force-pushed
the
add/ext-telemetry
branch
from
July 28, 2026 23:40
ab4b72a to
b23fe9f
Compare
inancgumus
temporarily deployed
to
azure-trusted-signing
July 28, 2026 23:45 — with
GitHub Actions
Inactive
inancgumus
temporarily deployed
to
azure-trusted-signing
July 28, 2026 23:47 — with
GitHub Actions
Inactive
janHildebrandt98
approved these changes
Jul 30, 2026
This was referenced Jul 30, 2026
inancgumus
added a commit
to grafana/k6-docs
that referenced
this pull request
Jul 31, 2026
The usage report now lists used extensions from the public extension catalog, and extension subcommands send a report of their own. Updates the usage collection page with the extensions entry, the catalog-only scope, the k6 x report, and which opt-outs cover it. Related: grafana/k6#6126 Related: grafana/k6#6229
inancgumus
added a commit
to grafana/k6-docs
that referenced
this pull request
Aug 4, 2026
* docs: document extension usage collection The usage report now lists used extensions from the public extension catalog, and extension subcommands send a report of their own. Updates the usage collection page with the extensions entry, the catalog-only scope, the k6 x report, and which opt-outs cover it. Related: grafana/k6#6126 Related: grafana/k6#6229 * docs: extension telemetry use admonition Co-authored-by: Heitor Tashiro Sergent <heitortsergent@gmail.com> --------- Co-authored-by: Heitor Tashiro Sergent <heitortsergent@gmail.com>
This was referenced Aug 6, 2026
social4hyq
pushed a commit
to social4hyq/homebrew-core
that referenced
this pull request
Sep 20, 2026
k6 2.2.0 Created-by: HarmonybrewBot Commit-by: HarmonybrewBot Merged-by: HarmonybrewBot Description: Created by `brew bump` --- Created with `brew bump-formula-pr`.<details> <summary>release notes</summary> <pre>k6 `v2.2.0` is here 🎉! This release includes: - `k6 cloud run --local-execution` now streams k6's logs to Grafana Cloud, so the test run's log view works for local execution too. - `chromium.connectOverCDP()`, which connects browser tests to an already-running Chromium instance. - `TextEncoder` and `TextDecoder` available as globals, and `WritableStream` support in `k6/experimental/streams`. - A `k6 cloud load-zone list` command. - Two new experimental feature flags: `merge-run-tags` and `freeze-env`. ## Breaking changes There are no breaking changes in this release. ## New features ### `k6 cloud run --local-execution` streams logs to Grafana Cloud [#6171](grafana/k6#6171) When a cloud test runs locally with `k6 cloud run --local-execution`, k6's logs now stream to the Grafana Cloud test run, so the run's log view is populated the same way it is for cloud execution. Previously, local-execution logs stayed on the machine running k6 and never reached the cloud. Work with Grafana's secrets management to safely work with secrets and redact them if they're accidentally leaked into logs are pushed. Use the new `--no-cloud-logs` flag opts out to opt out of streaming of logs when working with `--local-execution`: ```shell k6 cloud run --local-execution script.js k6 cloud run --local-execution --no-cloud-logs script.js ``` ### Connect to a running browser with `chromium.connectOverCDP()` [#6165](grafana/k6#6165) The browser module can now attach to an existing Chromium-based browser over the Chrome DevTools Protocol, mirroring Playwright's `browserType.connectOverCDP()`. Pass the browser's WebSocket endpoint and k6 manages the returned browser's connection — it's auto-closed at the end of the iteration, though you can call `close()` earlier to release the connection on demand. ```js import { chromium } from 'k6/browser'; export default async function () { const browser = await chromium.connectOverCDP('ws://localhost:9222/devtools/browser/<id>'); const page = await browser.newPage(); try { await page.goto('https://quickpizza.grafana.com/'); } finally { await page.close(); await browser.close(); } } ``` Unlike the `K6_BROWSER_WS_URL` environment variable, the endpoint is a runtime value — you can, for example, request a fresh session URL from a browser provider's API in `setup()` and connect to it from the iterations. ### `TextEncoder` and `TextDecoder` globals [#6182](grafana/k6#6182) `TextEncoder` and `TextDecoder` are now available as standard globals in both the init and VU contexts, no import required — matching how they are exposed in browsers and other JavaScript runtimes. ```js const encoded = new TextEncoder().encode('Hello, world!'); const decoded = new TextDecoder().decode(encoded); ``` ### `WritableStream` in `k6/experimental/streams` [#6132](grafana/k6#6132) The experimental streams module now implements `WritableStream` and `WritableStreamDefaultWriter` following the WHATWG Streams specification, complementing the existing `ReadableStream` and paving the way for a future `TransformStream` implementation. ```js import { WritableStream } from 'k6/experimental/streams'; export default async function () { const stream = new WritableStream({ write(chunk) { console.log(`wrote ${chunk}`); }, }); const writer = stream.getWriter(); await writer.write('hello'); await writer.close(); } ``` ### `k6 cloud load-zone list` command [#6142](grafana/k6#6142) A new `k6 cloud load-zone list` subcommand lists the load zones — public and private — available in the configured Grafana Cloud k6 stack, mirroring the existing `k6 cloud project list` command. Output defaults to a human-readable table; pass `--json` to emit a JSON array instead. ```console $ k6 cloud load-zone list Load zones for https://example.grafana.net: ID NAME TYPE AVAILABLE amazon:us:ashburn Ashburn, US (Amazon) public yes amazon:sa:cape town Cape Town, SA (Amazon) public yes ``` ### Configurable `handleSummary()` timeout [#5854](grafana/k6#5854) The time budget for the `handleSummary()` callback — previously hardcoded to 120 seconds — is now configurable through the `handleSummaryTimeout` option or the `K6_HANDLE_SUMMARY_TIMEOUT` environment variable, so long-running tests with heavy summaries no longer fail with `handleSummary() execution timed out`. Thanks, @LBaronceli! ```js export const options = { handleSummaryTimeout: '5m', }; ``` ### New experimental feature flags: `merge-run-tags` and `freeze-env` Two new experimental flags join the feature-flag system introduced in v2.1.0: - [#5714](grafana/k6#5714) `merge-run-tags` merges run tags per key across config layers, so `options.tags` in a script is no longer silently discarded when `--tag` or `K6_TAGS` is also used — higher-priority layers win on conflicting keys instead of replacing the whole map. Thanks, @yordis! - [#6032](grafana/k6#6032) `freeze-env` freezes the `__ENV` object, so modifications from script code throw a `TypeError` (in strict mode) instead of silently persisting across iterations and scenarios. Thanks, @lohitkolluri! ```shell k6 run --features merge-run-tags,freeze-env script.js ``` ## UX improvements and enhancements - [#5631](grafana/k6#5631) Makes the browser module's header accessors — `response.allHeaders()`, `headerValue()`, `headerValues()`, and `headersArray()` — return the raw wire headers (including `Set-Cookie` and security-related headers), correctly paired with each hop of a redirect chain instead of Chrome's provisional headers. As part of this, `headerValues()` now matches header names case-insensitively and splits repeated values on newlines rather than commas, and the `browser_data_sent`/`browser_data_received` metrics now include the raw header bytes and no longer vary run-to-run with CDP event ordering. - [#6208](grafana/k6#6208) Makes `k6 cloud` reject the run flags (for example, `--vus`) with an `unknown flag` error and a non-zero exit code. Previously `k6 cloud --vus 10 script.js` accepted the flags, printed the help text, and exited 0 — running tests with `k6 cloud` directly was deprecated in v2.0.0 in favor of `k6 cloud run`. - [#6096](grafana/k6#6096) Points the cloud secrets error at `K6_CLOUD_SECRETS_TOKEN` and `K6_CLOUD_SECRETS_ENDPOINT` when a test run is reused via `K6_CLOUD_PUSH_REF_ID` under `--local-execution`, instead of suggesting the `--local-execution` flag the user is already using. - [#6196](grafana/k6#6196) Adds `catch` blocks to the browser examples so a failing iteration reports the original error instead of a subsequent `page.close()` failure. Thanks, @locker95! ## Bug fixes - [#6234](grafana/k6#6234) Classifies HTTP/2 errors by message so the `error_code` metric tag stays correct when k6 is built with Go 1.27 (whose `x/net/http2` delegates to the standard library), and explicitly enables HTTP/2 negotiation on VU transports. - [#6232](grafana/k6#6232) Drains queued log entries in the Loki hook at shutdown so `--out loki` and cloud log streaming no longer lose the final batch, and emits a `k6 dropped N log messages` warning when the cloud log buffer overflows instead of dropping logs silently. - [#6125](grafana/k6#6125) Serializes the first concurrent open of a file in the caching filesystem so parallel `fs.open()` calls on the same file no longer read zero or truncated bytes. - [#6147](grafana/k6#6147) Fixes a data race and inconsistent request-interception state when browser routes are added or removed concurrently. Thanks, @somak2kai! - [#6070](grafana/k6#6070) Flushes buffered file log output once per second so recent logs aren't lost when k6 is killed before shutdown. Thanks, @rohan-patnaik! - [#6205](grafana/k6#6205) Stops sending an invalid `Sec-WebSocket-Protocol` header when tailing Grafana Cloud logs; spec-strict servers rejected the handshake with `websocket: bad handshake`. - [#6200](grafana/k6#6200) Leaves a counter's `rate` unset when the observed duration is zero, instead of computing `+Inf` and spuriously failing `rate` thresholds. Thanks, @samarth70! - [#6195](grafana/k6#6195) Initializes a gauge's maximum from the first sample so all-negative gauge series no longer report `max=0`. Thanks, @Solaris-star! - [#6145](grafana/k6#6145) Prevents the OpenTelemetry output from panicking at startup when basic auth is configured without `K6_OTEL_HEADERS`. Thanks, @lukdz! - [#6140](grafana/k6#6140) Stops `SharedArray` deep-freezing JS primitives, which needlessly wrapped large strings in `String` objects — cutting memory usage in the reported reproduction from roughly 1 GB to 100 MB. ## Maintenance and internal improvements - [#6126](grafana/k6#6126), [#6224](grafana/k6#6224), [#6229](grafana/k6#6229) Adds anonymous extension usage to the k6 usage report: a run reports the Go module path, version, and type of registry-cataloged extensions it actually uses (imported `k6/x/` modules, output extensions selected with `--out`, and `k6 x` subcommands). Private and unlisted extensions are never reported, and the existing `--no-usage-report` opt-out covers it. - [#6183](grafana/k6#6183), [#6218](grafana/k6#6218) Updates Sobek and regexp2, making `W See merge request: Harmonybrew/homebrew-core!16299
This branch was previously deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What?
Adds anonymous extension usage to the k6 usage report, covering the three user-facing surfaces:
k6/x/module) is reported when a run resolves it, not when it is merely compiled into the binary.--out; built-in outputs are unaffected.k6 x, whether it exits zero or non-zero.Each entry carries the extension's Go module path, version, and kind. Only extensions listed in the public registry catalog are reported: matching is by module path, so a private fork reusing a public import name is dropped, and an unreachable catalog reports nothing rather than falling back to unfiltered data. When no cataloged extension was used, the report omits the field entirely. The existing
--no-usage-report/K6_NO_USAGE_REPORTopt-out gates all of it; no new opt-out, no new endpoint, and the send stays bounded and non-fatal: it never fails a command, and a hanging endpoint delays exit by at most 3 seconds, the same bound thek6 runreport has always had.Why?
Extension adoption is a blind spot: the usage report tracks built-in modules but deliberately drops every extension, so decisions about which extensions to promote, maintain, or retire run on guesswork. Cloud-executed runs are partly tracked, while local execution, most OSS usage, stays invisible. Reporting catalog extensions through the same anonymous report k6 already sends closes that gap without a new data category: private and unlisted extensions never appear, xk6-built and provisioned binaries report identically, and a newly cataloged extension becomes reportable as soon as the catalog cache refreshes, with no new k6 release.
Note
K6_USAGE_REPORT_URL, makes the report endpoint overridable so the integration tests can assert on the actually-sent report; catalog membership reuses the existingK6_PROVISION_CATALOG_URLoverride.Checklist
make check) and all pass.Related PR(s)/Issue(s)