szkiba / xk6-output-plugin

Write k6 output extension as a dynamically loadable plugin using your favorite programming language

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

xk6-output-plugin

Write k6 output extension as dynamically loadable plugin

The xk6-output-plugin is a k6 output extension that allows the use of dynamically loaded output plugins.

k6 provides many options for output management. In addition, output extensions can be made to meet individual needs. However, a custom k6 build is required to use the output extensions, and the extensions can only be created in the go programming language. The xk6-output-plugin makes custom output handling simpler and more convenient.

Features

  • output plugins can be created using your favorite programming language (e.g. go, JavaScript, Python)
  • output plugins can be created and used without rebuilding the k6 executable
  • output plugins do not increase the size of the k6 executable
  • the output plugins can be distributed independently of the k6 binary

Examples

The examples directory contains examples of how to use different programming languages to write plugin.

Simple examples:

Python
import datetime
import logging

from xk6_output_plugin_py.output import serve, Output, Info, MetricType, ValueType

class Example(Output):
  def Init(self, params):
    logging.info("init")

    return Info(description="example-py plugin")

  def Start(self):
    logging.info("start")

  def Stop(self):
    logging.info("stop")

  def AddMetrics(self, metrics):
    logging.info("metrics")
    for metric in metrics:
      logging.info(
        metric.name,
        extra={
          "metric.type": MetricType.Name(metric.type),
          "metric.contains": ValueType.Name(metric.contains),
        },
      )

  def AddSamples(self, samples):
    logging.info("samples")
    for sample in samples:
      t = datetime.datetime.fromtimestamp(
        sample.time / 1000.0, tz=datetime.timezone.utc
      )

      logging.info(
        sample.metric,
        extra={"sample.time": t, "sample.value": sample.value},
      )

if __name__ == "__main__":
  serve(Example())
JavaScript
import { serve } from 'xk6-output-plugin-js'

class Example {
  init (params) {
    console.info('init')
    return { description: 'example-js plugin' }
  }

  start () {
    console.info('start')
  }

  stop () {
    console.info('stop')
  }

  addMetrics (metrics) {
    console.info('metrics')
    metrics.forEach(metric => {
      console.info(
        { 'metric.type': metric.type, 'metric.contains': metric.contains },
        metric.name
      )
    })
  }

  addSamples (samples) {
    console.info('samples')
    samples.forEach(sample => {
      console.info(
        { 'sample.time': new Date(sample.time).toISOString(), 'sample.value': sample.value },
        sample.metric
      )
    })
  }
}

serve(new Example())
go
package main

import (
  "context"
  "time"

  "github.com/hashicorp/go-hclog"
  "github.com/szkiba/xk6-output-plugin-go/output"
)

type example struct{}

func (e *example) Init(ctx context.Context, params *output.Params) (*output.Info, error) {
  hclog.L().Info("init")

  return &output.Info{Description: "example-go plugin"}, nil // nolint:exhaustruct
}

func (e *example) Start(ctx context.Context) error {
  hclog.L().Info("start")

  return nil
}

func (e *example) Stop(ctx context.Context) error {
  hclog.L().Info("stop")

  return nil
}

func (e *example) AddMetrics(ctx context.Context, metrics []*output.Metric) error {
  hclog.L().Info("metrics")

  for _, metric := range metrics {
    hclog.L().Info(metric.Name,
      "metric.type", metric.Type.String(),
      "metric.contains", metric.Contains.String(),
    )
  }

  return nil
}

func (e *example) AddSamples(ctx context.Context, samples []*output.Sample) error {
  hclog.L().Info("samples")

  for _, sample := range samples {
    hclog.L().Info(sample.Metric,
      "sample.time", time.UnixMilli(sample.Time).Format(time.RFC3339),
      "sample.value", sample.Value,
    )
  }

  return nil
}

func main() {
  output.Serve(new(example))
}

Why not JSON?

A similar approach to the output plugin can also be achieved by processing the output generated by the k6 json output extension by an external program.

Why use the output plugin instead of processing JSON output?

  • type safe API
  • simpler callback-based processing
  • structured logging to k6 log output
  • real time result processing

How It Works?

The plugin is a local gRPC service that the xk6-output-plugin starts automatically during k6 startup and stops before k6 stops.

Output plugins are managed using the HashiCorp go-plugin.

Performance

Metric samples are buffered by the xk6-output-plugin for one second (by default) and then the plugin is called. The call per second is frequent enough for real-time processing, yet infrequent enough not to cause performance issues while running the test. The grpc call itself takes place locally through a continuously open connection, so its overhead is minimal.

The duration of buffering can be set by the plugin with the buffering variable in the Init response. Its value specifies the buffering duration in milliseconds. The default is 1000ms, which is one second. Its minimum value is 200ms.

Poliglot

One of the big advantages of output plugins is that practically any programming language can be used, which is supported by grpc for server writing.

Although the go programming language is popular, its popularity does not reach the popularity of, for example, Python or JavaScript languages. With the support of these languages, the range of developers who can create an output plugin in their favorite programming language has been significantly expanded.

Helpers

Based on the protobuf descriptor and the HashiCorp go-plugin documentation, the output plugin can be created in any programming language supported by gRPC.

Plugin development is facilitated by a helper package in the following programming languages:

Usage

The output plugin is an executable program whose name must (for security reasons) begin with the string xk6-output-plugin-. This is followed by the actual name of the plugin, which can be used to refer to it. The reference can of course also contain the full name with the prefix xk6-output-plugin-. The two reference below specify the same plugin:

./k6 run --out=plugin=example script.js
./k6 run --out=plugin=xk6-output-plugin-example script.js

The plugin is run by taking the PATH environment variable into account, unless the reference also contains a path. The reference below runs the file named xk6-output-plugin-example from the plugins directory in the current directory:

./k6 run --out=plugin=./plugins/example script.js

Configuration

Output plugins can be configured using command line arguments. Arguments can be passed to the plugin in the usual way after the name of the plugin. In this case, the entire k6 output extension parameter must be protected with apostrophes.

./k6 run --out='plugin=example -flag value' script.js

The output plugin can also be configured with environment variables. The environment variables specified during the execution of the k6 command are received by the plugin via the Init() call. These variables are available in the map named Environment of the Params parameter.

./k6 run -e var=value --out=plugin=example script.js

Download

You can download pre-built k6 binaries from Releases page. Check Packages page for pre-built k6 Docker images.

Build

To build a k6 binary with this extension, first ensure you have the prerequisites:

Then:

  1. Download xk6:
$ go install go.k6.io/xk6/cmd/xk6@latest
  1. Build the binary:
$ xk6 build --with github.com/szkiba/xk6-output-plugin@latest

Docker

You can also use pre-built k6 image within a Docker container. In order to do that, you will need to execute something like the following:

Linux

docker run -v $(pwd):/scripts -it --rm ghcr.io/szkiba/xk6-output-plugin:latest run --out=plugin=XXX /scripts/script.js

Windows

docker run -v %cd%:/scripts -it --rm ghcr.io/szkiba/xk6-output-plugin:latest run --out=plugin=XXX /scripts/script.js

About

Write k6 output extension as a dynamically loadable plugin using your favorite programming language

License:MIT License


Languages

Language:Go 97.7%Language:JavaScript 2.3%