Skip to main content

k6 Grafana Dashboard: InfluxDB, Prometheus and OTel

k6 Grafana dashboard setups tested on k6 v2.4.0: the two deprecated outputs, why default OpenTelemetry buckets showed a 307 ms p95 as 487.5 ms, and the fix.

k6 Grafana Dashboard: InfluxDB, Prometheus and OTel

To get k6 results into a Grafana dashboard, k6 streams its metrics to a time-series database during the test and Grafana reads them from there. k6 has three built-in ways to do it: the InfluxDB v1 output, the Prometheus remote-write output and the OpenTelemetry output. Since k6 v2.4.0, released on 12 November 2026, the first two are deprecated and planned for removal in k6 v3.

We ran the same 50-second, 20-user test through all three outputs on k6 v2.4.0, into InfluxDB 1.8 and Prometheus 3.7.3, and loaded Grafana 12.3.0 with the dashboards most guides point to. Three results stood out: the recommended OpenTelemetry route filled only 5 of 13 panels on either official k6 dashboard, its default histogram showed a 307 ms p95 as 487.5 ms, and InfluxDB silently kept 34,698 of 35,447 request points.

Key Takeaways

  • k6 v2.4.0 prints a deprecation warning for -o influxdb and -o experimental-prometheus-rw, and recommends -o opentelemetry. Both old outputs still work in v2.4.0.
  • The two official k6 dashboards on grafana.com (19665 and 18030) were built for Prometheus remote write. With OpenTelemetry metrics, only the VU and request-count panels had data; latency, checks and transfer panels stayed empty because the metric names differ.
  • OpenTelemetry’s default histogram buckets jump from 250 to 500 ms. At a real p95 of 307 ms, Grafana showed 487.5 ms. Setting OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION=base2_exponential_bucket_histogram brought it to 304.4 ms against k6’s own 304.1 ms.
  • The Prometheus remote-write output sends only p(99) by default, so the k6 Prometheus dashboard’s latency panels are empty at p95 until you set K6_PROMETHEUS_RW_TREND_STATS.
  • InfluxDB v1 overwrites points with the same tags and timestamp. On our macOS machine that cost 2.1% to 3.6% of request points in two runs; a per-VU tag fixed it at about 18 times the series count.

Which k6 output should you use for a Grafana dashboard?

Use the OpenTelemetry output for anything new: it is the one k6 is keeping, its counts were exact in our runs, and any OTLP backend can store it. It needs exponential histograms for accurate percentiles and its own queries, because the published dashboards do not match its metric names. Prometheus remote write still works best with those dashboards, until k6 v3 removes it.

This is what each output gave us for the same test: 20 virtual users ramping up over 15 seconds, 30 seconds at 20, 5 seconds down, about 36,000 requests, 1 in 20 of them sent to an endpoint that returns 500.

Output (k6 v2.4.0)StatusWhat reached the databasePanels with data in dashboard 19665 / 18030 (13 each)Watch out for
-o influxdb (InfluxDB v1)DeprecatedOne point per sample, 16 measurements2587 is a separate 12-panel community dashboard (2017)Points with equal tags and timestamp overwrite each other
-o experimental-prometheus-rw, default statsDeprecated25 series; trends as _p99 gauges only9 / 8p95 panels empty
Same, K6_PROMETHEUS_RW_TREND_STATS=p(95),p(99),min,max,avg,medDeprecated100 series13 / 8Four times the series
Same, native histogramsDeprecated25 series; trends as k6_*_seconds native histograms8 / 13Needs native histograms enabled in Prometheus
-o opentelemetry into PrometheusRecommended281 series; trends as k6_*_milliseconds buckets5 / 5Default buckets distort percentiles

The two official dashboards are k6 Prometheus (19665) and k6 Prometheus (Native Histograms) (18030), both published by the k6 team. A search of grafana.com for “k6 opentelemetry” and “k6 otel” on 14 November 2026 returned no dashboards, so the OpenTelemetry route means writing your own panels. The queries we used are below.

What changed for k6 outputs in v2.4.0?

k6 v2.4.0 deprecated its built-in InfluxDB output and its experimental Prometheus remote-write output, and plans to remove both in k6 v3. They still run in v2.4.0, but each now logs a warning at the start of every test. The release notes recommend migrating to the OpenTelemetry output. Our runs printed this, and still exited 0:

level=warning msg="influxdb output has been deprecated and is planned for removal in k6 v3. We recommend using opentelemetry for all your metrics output."

The remote-write output prints the same sentence with its own name. A second warning appeared when we enabled native histograms the documented way, with K6_PROMETHEUS_RW_TREND_AS_NATIVE_HISTOGRAM=true: Legacy env var detected, use --features or K6_FEATURES instead, naming the feature native-histograms. The setting still took effect.

Nothing changes for a dashboard that is already working, until you upgrade to v3. The reason to act now is that any new setup built on InfluxDB or remote write will need rebuilding, and the queries do not carry over, as the next sections show.

How do you set up a k6 Grafana dashboard with Prometheus?

Run Prometheus with its remote-write receiver turned on, point k6’s remote-write output at it, and import the k6 Prometheus dashboard into Grafana. Tag each run with a testid so the dashboard’s selector can tell runs apart. This is the setup most tutorials show, and it is the one the official dashboards match.

docker run -d -p 9090:9090 prom/prometheus:v3.7.3 \
  --config.file=/etc/prometheus/prometheus.yml \
  --web.enable-remote-write-receiver \
  --web.enable-otlp-receiver \
  --enable-feature=native-histograms

K6_PROMETHEUS_RW_SERVER_URL=http://localhost:9090/api/v1/write \
K6_PROMETHEUS_RW_TREND_STATS='p(95),p(99),min,max,avg,med' \
k6 run -o experimental-prometheus-rw --tag testid=run1 script.js

The trend stats line is the one people miss. By default the output turns each trend metric, such as http_req_duration, into a single p99 gauge. Dashboard 19665 picks the statistic from a drop-down. Set to p95, it filled 9 of its 13 panels from our default run: the four latency panels came back empty, because no k6_http_req_duration_p95 series existed. At p99 they had data. With the six stats above, all 13 panels had data, at the cost of 100 series instead of 25.

The native-histogram variant sends each trend as one histogram, which dashboard 18030 reads with histogram_quantile(). It filled 13 of 13 panels there, and only 8 on dashboard 19665. Pick the dashboard that matches the setting, not the other way round. The native-histogram p95 read 23.9 ms against k6’s own 22.8 ms; the gauge route read 23.05 ms against 23.03 ms, because k6 computes those gauges itself.

How do you send k6 metrics to Grafana with OpenTelemetry?

Run k6 with -o opentelemetry and point it at an OTLP endpoint: an OpenTelemetry Collector, Grafana Cloud’s OTLP gateway, or Prometheus 3’s own OTLP receiver, which is what we used. Grafana then reads Prometheus as usual. The metric names change on the way, so the published dashboards’ latency and checks panels find nothing.

K6_OTEL_EXPORTER_PROTOCOL=http/protobuf \
K6_OTEL_HTTP_EXPORTER_INSECURE=true \
K6_OTEL_HTTP_EXPORTER_ENDPOINT=localhost:9090 \
K6_OTEL_HTTP_EXPORTER_URL_PATH=/api/v1/otlp/v1/metrics \
K6_OTEL_METRIC_PREFIX=k6_ \
OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION=base2_exponential_bucket_histogram \
k6 run -o opentelemetry --tag testid=run1 script.js

The default protocol is gRPC to localhost:4317; the lines above switch to HTTP because that is what Prometheus accepts. The metric prefix is empty by default, so without K6_OTEL_METRIC_PREFIX the names carry no k6_. The last variable is explained in the next section; leave it in.

With the prefix set, Prometheus stored k6_http_reqs_total and k6_vus under the same names the dashboards use, which is why the VU, request count and request rate panels worked. Everything else differed. Trends arrived with their unit in the name (k6_http_req_duration_milliseconds), data counters became k6_data_sent_bytes_total, and each rate metric became a single counter with a condition label. These queries returned the right numbers for our runs, checked against k6’s end-of-test summary:

PanelPromQL for k6 OpenTelemetry metrics
Request ratesum(rate(k6_http_reqs_total{testid="$testid"}[1m]))
p95 latency (exponential histograms)histogram_quantile(0.95, sum(rate(k6_http_req_duration_milliseconds{testid="$testid"}[1m])))
Error ratesum(rate(k6_http_req_failed_total{testid="$testid",condition="nonzero"}[1m])) / sum(rate(k6_http_req_failed_total{testid="$testid"}[1m]))
Check pass ratesum(k6_checks_total{testid="$testid",condition="nonzero"}) / sum(k6_checks_total{testid="$testid"})
Active VUssum(k6_vus{testid="$testid"})

Read condition carefully: nonzero means the rate metric’s value was true. For http_req_failed that is a failed request, and for checks it is a passed check. Our run had 1,891 nonzero failures and 34,110 nonzero checks, matching k6’s 5.25% failed requests and 94.74% passed checks. The pair of .occurred and .total counters described in older guides is gone; k6’s documentation now describes only the single counter with a condition attribute.

Why are k6 percentiles wrong in Grafana with OpenTelemetry?

Because the OpenTelemetry output’s default histogram uses fixed buckets, and Prometheus can only estimate a percentile inside a bucket. The default boundaries are 0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1,000, 2,500, 5,000, 7,500 and 10,000 milliseconds, so every response between 250 and 500 ms lands in one bucket. Exponential histograms remove the problem.

At our test API’s normal 20 ms, the error was small: Grafana’s p95 was 24.5 ms against k6’s 23.7 ms. Then we slowed the API to about 300 ms and ran 20 users for 30 seconds. All 2,000 responses fell between 299 and 318 ms, inside the 250-500 bucket, and histogram_quantile() spread them evenly across it:

Statistick6 summaryGrafana, default bucketsGrafana, exponential histogram
Median302.5 ms375 ms302.5 ms
p95307.1 ms487.5 ms304.4 ms (k6: 304.1 ms on that run)
p99not in the summary497.5 ms316.4 ms (k6 max: 317.2 ms)

A p95 SLO of 400 ms would have looked broken on a service that never went above 318 ms. The fix is the standard OpenTelemetry SDK variable OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION=base2_exponential_bucket_histogram, which k6’s OpenTelemetry output accepts. Prometheus stored the result as a native histogram (our 3.7.3 ran with --enable-feature=native-histograms), and the query drops the _bucket suffix, as in the p95 query in the PromQL table earlier. We ran the second test with only that variable changed. For why the p95 is the number to watch, see percentiles versus averages.

Does the k6 InfluxDB output lose data points?

It can. InfluxDB v1 treats two points with the same measurement, tag set and timestamp as one point and keeps the last. k6’s default tags do not include the VU number, so two requests from different VUs that finish in the same instant with the same URL and status are written as one. Every write returned HTTP 204, and k6 logged no error.

On our first InfluxDB run, http_req_duration held 35,364 points for a test that made 36,671 requests. To confirm the cause, we ran again with -o json alongside the InfluxDB output. The JSON file held 35,447 request points, of which 34,698 had a unique combination of timestamp and tags, and InfluxDB held exactly 34,698. The timestamps on this macOS machine had microsecond resolution, so requests finishing close together could share one.

Adding the VU number back did not help: in k6 v2.4.0, vu is not a tag, and with it listed in K6_SYSTEM_TAGS our JSON output still carried no vu tag and InfluxDB stored no vu key. A tag set from the script did. With exec.vu.metrics.tags.vu_id = String(exec.vu.idInTest) at the top of the default function, InfluxDB kept 34,809 of 34,809 points, and the series count grew from 25 to 462. The Prometheus and OpenTelemetry outputs aggregate before they send, so this overwrite does not apply to them; the OpenTelemetry request count matched k6’s summary exactly.

Can you stream LoadFocus k6 runs to Grafana?

Yes. LoadFocus has an OpenTelemetry integration that streams the live metrics of a k6 test to any OTLP backend, including Grafana Cloud and a self-hosted OpenTelemetry Collector. You create the integration once under Settings, Integrations, attach it to a test, and every run tagged with it sends its metrics while it runs.

According to the OpenTelemetry integration guide, k6 tests send k6’s standard metrics, such as http_req_duration, http_reqs, vus and iterations, tagged with the test run name and id, the region and the machine, so one dashboard can show a run that LoadFocus spread over several regions. Rate metrics arrive as one counter each with the same condition attribute described above, so the same logic applies; filter on testrunid instead of testid, and use the metric names your backend actually stores. JMeter tests can use the same integration, with their metrics arriving once the run finishes. The runs themselves happen on LoadFocus’s cloud machines from 25 locations, as the k6 load testing page describes. We did not run a LoadFocus test into our lab Prometheus for this guide; the setup steps above are for k6 on your own machines.

For local runs where you do not need a time-series database at all, the built-in web dashboard and its HTML export in our k6 reports guide are less work. And when one machine is not enough, the k6 Operator runs the test as several pods, each streaming to the same backend.

Frequently Asked Questions

Is the k6 InfluxDB output deprecated?

Yes. Since k6 v2.4.0, released on 12 November 2026, the built-in influxdb output and the experimental Prometheus remote-write output are deprecated and planned for removal in k6 v3. They still work in v2.4.0 and log a warning at the start of each test. k6 recommends the OpenTelemetry output instead.

Which Grafana dashboard should I use for k6?

For Prometheus remote write, use dashboard 19665 (k6 Prometheus) with the trend stats you need, or 18030 if you send native histograms. For the OpenTelemetry output, no published dashboard matched the metric names in our test on 14 November 2026, so build the panels yourself from the OpenTelemetry metric names: trend metrics carry their unit in the name, such as milliseconds, and rate metrics are single counters with a condition label.

Why are my k6 latency panels empty in Grafana?

Usually because the metric names in Prometheus do not match the dashboard. With remote write, the default sends only p99, so p95 panels find nothing until you set K6_PROMETHEUS_RW_TREND_STATS. With OpenTelemetry, trend metrics get a unit in their name, so the k6 Prometheus dashboards’ latency panels stay empty.

Why is the k6 p95 in Grafana higher than in the k6 summary?

If the metrics came through OpenTelemetry with default settings, Grafana estimates percentiles from wide fixed buckets. In our test a real p95 of 307 ms showed as 487.5 ms. Set OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION to base2_exponential_bucket_histogram, and the estimate came within 0.3 ms of k6’s own p95 (304.4 ms against 304.1 ms).

How do I send k6 metrics to Prometheus with OpenTelemetry?

Start Prometheus 3 with its OTLP receiver enabled, then run k6 with the opentelemetry output, the HTTP protocol, the endpoint set to the Prometheus host and port, and the URL path /api/v1/otlp/v1/metrics. Add the k6_ metric prefix and exponential histograms so names and percentiles match what your queries expect.

Sources

  • k6 v2.4.0 release notes (12 November 2026), including the deprecation of the influxdb and experimental-prometheus-rw outputs: github.com/grafana/k6/releases, read 14 November 2026.
  • Grafana k6 documentation: OpenTelemetry output, Prometheus remote write output and InfluxDB output, grafana.com/docs/k6/latest/results-output/real-time/, read 14 November 2026.
  • Dashboards: k6 Prometheus (19665, revision 3), k6 Prometheus (Native Histograms) (18030, revision 8) and k6 Load Testing Results (2587), JSON downloaded from grafana.com/grafana/dashboards, 14 November 2026.
  • LoadFocus OpenTelemetry integration: OpenTelemetry (OTLP) Integration and loadfocus.com/k6-load-testing, read 14 November 2026.
  • Test runs: k6 v2.4.0 (official macOS arm64 release, checksum-verified) on an Apple M1 Max, with InfluxDB 1.8, Prometheus v3.7.3 and Grafana 12.3.0 in Docker, against a local Node.js API answering in 20 ms (and 300 ms for the percentile test), 14 November 2026. Five 50-second, 20-user runs (one per output setting), three InfluxDB runs with JSON output alongside (the cross-check and two fix attempts), and two 30-second runs for the histogram comparison. Panel coverage was measured by running each dashboard panel’s queries against each run.

Bogdan

Founder at LoadFocus

Bogdan builds and runs the tools this blog is about. He writes from what the products actually do in production, including the parts that break.

Weekly tips right in your inbox.

Don’t miss out on the latest testing tips and tricks, tools and news related to software testing.

Already +20,000 members in the list.

The Load Testing Platform with AI That Explains Your Results

Load test websites and APIs from 25 cloud regions, monitor page speed and uptime, and get AI analysis that explains your results in plain English.

Cloud load testing, website speed and API monitoring in one place.

Hi there! Can I share a cool thing I'm working on with you?

A software testing newsletter that sparks joy.

Our goal is to create helpful content for software developers and testers.

If you're not into it, you can unsubscribe instantly.