Important: This documentation is about an older version. It's relevant only to the release noted, many of the features and functions have been updated or replaced. Please view the current version.
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.