Output Extensions
k6 provides many metrics and output formats, but it cannot directly support all possibilities. To store or alter metrics captured during an active k6 test, you can create a custom output extension.
Output extension binaries can use the --out
flag to send metrics to a custom place.
Some potential reasons for a custom extension could include:
- To support a time-series database not already supported
- To add derived metrics data for storage
- To filter metrics to only the data you care about
Like JavaScript extensions, output extensions rely on the extension author to implement specific APIs.
Before you start:
To run this tutorial, you’ll need the following applications installed:
- Go
- Git
You also need to install xk6:
$ go install go.k6.io/xk6/cmd/xk6@latest
Write a simple extension
- Set up a directory to work in.
$ mkdir xk6-output-logger; cd xk6-output-logger; go mod init xk6-output-logger
- The core of an Output extension is a struct that implements the
output.Output
interface.
Create a simple example that outputs each set of metrics to the console as received by the AddMetricSamples(samples []metrics.SampleContainer)
method of the output interface.
package log
import (
"fmt"
"strings"
"time"
"go.k6.io/k6/metrics"
"go.k6.io/k6/output"
)
// AddMetricSamples receives metric samples from the k6 Engine as they're emitted.
func (l *Logger) AddMetricSamples(samples []metrics.SampleContainer) {
for _, sample := range samples {
all := sample.GetSamples()
fmt.Fprintf(l.out, "%s [%s]\n", all[0].GetTime().Format(time.RFC3339Nano), metricKeyValues(all))
}
}
// metricKeyValues returns a string of key-value pairs for all metrics in the sample.
func metricKeyValues(samples []metrics.Sample) string {
names := make([]string, 0, len(samples))
for _, sample := range samples {
names = append(names, fmt.Sprintf("%s=%v", sample.Metric.Name, sample.Value))
}
return strings.Join(names, ", ")
}
- Register the module to use these from k6 test scripts.
import "go.k6.io/k6/output"
// init is called by the Go runtime at application startup.
func init() {
output.RegisterExtension("logger", New)
}
Note
You must use the registered with the-o
, or--out
flag when running k6!
The final extension code looks like this:
package log
import (
"fmt"
"io"
"strings"
"time"
"go.k6.io/k6/metrics"
"go.k6.io/k6/output"
)
// init is called by the Go runtime at application startup.
func init() {
output.RegisterExtension("logger", New)
}
// Logger writes k6 metric samples to stdout.
type Logger struct {
out io.Writer
}
// New returns a new instance of Logger.
func New(params output.Params) (output.Output, error) {
return &Logger{params.StdOut}, nil
}
// Description returns a short human-readable description of the output.
func (*Logger) Description() string {
return "logger"
}
// Start initializes any state needed for the output, establishes network
// connections, etc.
func (o *Logger) Start() error {
return nil
}
// AddMetricSamples receives metric samples from the k6 Engine as they're emitted.
func (l *Logger) AddMetricSamples(samples []metrics.SampleContainer) {
for _, sample := range samples {
all := sample.GetSamples()
fmt.Fprintf(l.out, "%s [%s]\n", all[0].GetTime().Format(time.RFC3339Nano), metricKeyValues(all))
}
}
// metricKeyValues returns a string of key-value pairs for all metrics in the sample.
func metricKeyValues(samples []metrics.Sample) string {
names := make([]string, 0, len(samples))
for _, sample := range samples {
names = append(names, fmt.Sprintf("%s=%v", sample.Metric.Name, sample.Value))
}
return strings.Join(names, ", ")
}
// Stop finalizes any tasks in progress, closes network connections, etc.
func (*Logger) Stop() error {
return nil
}
Notice a couple of things:
The module initializer
New()
receives an instance ofoutput.Params
. With this object, the extension can access the output-specific configuration, interfaces to the filesystem, synchronized stdout and stderr, and more.AddMetricSamples
in this example writes to stdout. This output might have to be buffered and flushed periodically in a real-world scenario to avoid memory leaks. Below we’ll discuss some helpers you can use for that.
Compile your extended k6
To build a k6 binary with this extension, run:
$ xk6 build --with xk6-output-logger=.
Note
xk6-output-logger
is the Go module name passed togo mod init
Usually, this would be a URL similar to
github.com/grafana/xk6-output-logger
.
Use your extension
Now we can use the extension with a test script.
- In new JavaScript file, make some simple test logic.
import http from 'k6/http';
import { sleep } from 'k6';
export default function () {
http.get('https://test-api.k6.io');
sleep(0.5);
}
- Now, run the test.
$ ./k6 run test.js --out logger --quiet --no-summary --iterations 2
Note
The--out logger
argument tells k6 to use your custom output. The flag--quiet --no-summary
configures k6 to show only custom output.
Your output should look something like this:
2022-07-01T08:55:09.59272-05:00 [http_reqs=1, http_req_duration=117.003, http_req_blocked=558.983, http_req_connecting=54.135, http_req_tls_handshaking=477.198, http_req_sending=0.102, http_req_waiting=116.544, http_req_receiving=0.357, http_req_failed=0]
2022-07-01T08:55:09.917036-05:00 [vus=1, vus_max=1]
2022-07-01T08:55:10.094196-05:00 [data_sent=446, data_received=21364, iteration_duration=1177.505083, iterations=1]
2022-07-01T08:55:10.213926-05:00 [http_reqs=1, http_req_duration=119.122, http_req_blocked=0.015, http_req_connecting=0, http_req_tls_handshaking=0, http_req_sending=0.103, http_req_waiting=118.726, http_req_receiving=0.293, http_req_failed=0]
2022-07-01T08:55:10.715323-05:00 [data_sent=102, data_received=15904, iteration_duration=620.862459, iterations=1]
Things to keep in mind
- Output structs can optionally implement additional interfaces that allow them to receive thresholds or run-status updates and even interrupt a test.
- Consider using
output.SampleBuffer
andoutput.PeriodicFlusher
to improve performance given the large amounts of data produced by k6. Refer tostatsd
output for an example. - Use the project template as a starting point for your output extension.
Questions? Feel free to join the discussion on extensions in the k6 Community Forum.