Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 14 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,13 @@ k6 run -o 'prometheus=param1=value1&param2=value2' script.js
> [!TIP]
> Use quotes around the `--out` parameter to escape `&` characters from the shell.

| Parameter | Description | Default |
|-------------|-----------------------------------------------------------------------------------------------|----------------|
| `namespace` | [Prometheus namespace](https://prometheus.io/docs/practices/naming/) for exported metrics | `""` (empty) |
| `subsystem` | [Prometheus subsystem](https://prometheus.io/docs/practices/naming/) for exported metrics | `""` (empty) |
| `host` | Hostname or IP address for HTTP endpoint (empty = listen on all interfaces) | `""` (all) |
| `port` | TCP port for HTTP endpoint | `5656` |
| Parameter | Description | Default |
|------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------|
| `namespace` | [Prometheus namespace](https://prometheus.io/docs/practices/naming/) for exported metrics | `""` (empty) |
| `subsystem` | [Prometheus subsystem](https://prometheus.io/docs/practices/naming/) for exported metrics | `""` (empty) |
| `host` | Hostname or IP address for HTTP endpoint (empty = listen on all interfaces) | `""` (all) |
| `port` | TCP port for HTTP endpoint | `5656` |
| `usehistogramfortime` | If set to `'true'` or `'yes'`, sets the metric type for trends to a [histogram](https://prometheus.io/docs/concepts/metric_types/#histogram) instead of a [summary](https://prometheus.io/docs/concepts/metric_types/#summary) | `"no"` (uses summary) |

> [!TIP]
> It's recommended to use `k6` as either `namespace` or `subsystem` to prefix metrics with `k6_`.
Expand Down Expand Up @@ -173,16 +174,17 @@ export default function () {

The extension exports k6 metrics in Prometheus text format. Metric types are mapped as follows:

| k6 Metric Type | Prometheus Type | Description |
|----------------|-----------------|------------------------------------------------|
| Counter | Counter | Cumulative metric that only increases |
| Gauge | Gauge | Metric that can go up or down |
| Rate | Histogram | Ratio of non-zero values (exported as 0 or 1) |
| Trend | Summary | Statistical aggregations with quantiles |
| k6 Metric Type | Prometheus Type | Description |
|----------------|-------------------------|------------------------------------------------|
| Counter | Counter | Cumulative metric that only increases |
| Gauge | Gauge | Metric that can go up or down |
| Rate | Histogram | Ratio of non-zero values (exported as 0 or 1) |
| Trend | Summary (or Histogram) | Statistical aggregations with quantiles |

### Metric Labels

All k6 metric tags are preserved as Prometheus labels:

- `scenario` - Test scenario name
- `group` - Test group name
- `method` - HTTP method (for HTTP metrics)
Expand Down
53 changes: 38 additions & 15 deletions internal/prometheus.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ import (

// PrometheusAdapter is an adapter for Prometheus metrics.
type PrometheusAdapter struct {
Subsystem string
Namespace string
logger logrus.FieldLogger
metrics map[string]any
registry *prometheus.Registry
Subsystem string
Namespace string
logger logrus.FieldLogger
metrics map[string]any
registry *prometheus.Registry
UseHistogramForTime bool
}

type labelNames []string
Expand All @@ -42,6 +43,10 @@ type histogramWithLabels struct {
labelNames labelNames
}

var (
durationBuckets = []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 20, 50, 100, 250, 500, 1000, 2500, 5000, 10000}
)

// NewPrometheusAdapter creates a new PrometheusAdapter instance.
func NewPrometheusAdapter(registry *prometheus.Registry, logger logrus.FieldLogger, ns, sub string) *PrometheusAdapter {
return &PrometheusAdapter{
Expand Down Expand Up @@ -145,19 +150,14 @@ func (a *PrometheusAdapter) handleGauge(sample *metrics.Sample) {
}

func (a *PrometheusAdapter) handleRate(sample *metrics.Sample) {
if histogram := a.getHistogram(sample.Metric.Name, "k6 rate", []float64{0}, sample.Tags); histogram != nil {
labelValues := a.tagsToLabelValues(histogram.labelNames, sample.Tags)
a.handleHistogram(sample, []float64{0}, "k6 rate")
}

metric, err := histogram.histogramVec.GetMetricWithLabelValues(labelValues...)
if err != nil {
a.logger.Error(err)
} else {
metric.Observe(sample.Value)
}
}
func (a *PrometheusAdapter) handleTrendAsHistogram(sample *metrics.Sample) {
a.handleHistogram(sample, durationBuckets, "k6 trend")
}

func (a *PrometheusAdapter) handleTrend(sample *metrics.Sample) {
func (a *PrometheusAdapter) handleTrendAsSummary(sample *metrics.Sample) {
if summary := a.getSummary(sample.Metric.Name, "k6 trend", sample.Tags); summary != nil {
labelValues := a.tagsToLabelValues(summary.labelNames, sample.Tags)

Expand All @@ -168,7 +168,9 @@ func (a *PrometheusAdapter) handleTrend(sample *metrics.Sample) {
metric.Observe(sample.Value)
}
}
}

func (a *PrometheusAdapter) handleTrend(sample *metrics.Sample) {
if gauge := a.getGauge(sample.Metric.Name+"_current", "k6 trend (current)", sample.Tags); gauge != nil {
labelValues := a.tagsToLabelValues(gauge.labelNames, sample.Tags)

Expand All @@ -179,6 +181,14 @@ func (a *PrometheusAdapter) handleTrend(sample *metrics.Sample) {
metric.Set(sample.Value)
}
}

if a.UseHistogramForTime {
if sample.Metric.Contains == metrics.Time {
a.handleTrendAsHistogram(sample)
return
}
}
a.handleTrendAsSummary(sample)
}

func (a *PrometheusAdapter) getCounter( //nolint:dupl
Expand Down Expand Up @@ -336,6 +346,19 @@ func (a *PrometheusAdapter) getHistogram(
return histogram
}

func (a *PrometheusAdapter) handleHistogram(sample *metrics.Sample, durationBucket []float64, helpSuffix string) {
if histogram := a.getHistogram(sample.Metric.Name, helpSuffix, durationBucket, sample.Tags); histogram != nil {
labelValues := a.tagsToLabelValues(histogram.labelNames, sample.Tags)

metric, err := histogram.histogramVec.GetMetricWithLabelValues(labelValues...)
if err != nil {
a.logger.Error(err)
} else {
metric.Observe(sample.Value)
}
}
}

func helpFor(name string, helpSuffix string) string {
if h, ok := builtinMetrics[name]; ok {
return h
Expand Down
23 changes: 15 additions & 8 deletions prometheus.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ func init() {
}

type options struct {
Port int
Host string
Subsystem string
Namespace string
Port int
Host string
Subsystem string
Namespace string
UseHistogramForTime string
}

// Output is the Prometheus output implementation.
Expand Down Expand Up @@ -60,6 +61,10 @@ func (o *Output) Description() string {
return fmt.Sprintf("prometheus (%s)", o.addr)
}

func isOptTrue(s string) bool {
return s == "true" || s == "yes"
}

// Start implements output.Output.
func (o *Output) Start() error {
opts, err := getopts(o.arg)
Expand All @@ -70,6 +75,7 @@ func (o *Output) Start() error {
o.Namespace = opts.Namespace
o.Subsystem = opts.Subsystem
o.addr = fmt.Sprintf("%s:%d", opts.Host, opts.Port)
o.UseHistogramForTime = isOptTrue(opts.UseHistogramForTime)

listener, err := new(net.ListenConfig).Listen(context.TODO(), "tcp", o.addr)
if err != nil {
Expand All @@ -94,10 +100,11 @@ func (o *Output) Stop() error {

func getopts(query string) (*options, error) {
opts := &options{
Port: defaultPort,
Host: "",
Namespace: "",
Subsystem: "",
Port: defaultPort,
Host: "",
Namespace: "",
Subsystem: "",
UseHistogramForTime: "no",
}

if query == "" {
Expand Down