From 78f600c8006d3d2e1ed55e6cf6be9b364d79aaf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Miri=C4=87?= Date: Mon, 4 Oct 2021 12:34:59 +0200 Subject: [PATCH 01/14] Add xk6 documentation --- .../en/07 Misc/05 k6 Extensions.md | 197 +++++++++++++++++- 1 file changed, 195 insertions(+), 2 deletions(-) diff --git a/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md b/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md index 1d49416863..b3565b3708 100644 --- a/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md +++ b/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md @@ -1,4 +1,197 @@ --- title: 'k6 Extensions' -redirect: 'https://k6.io/blog/extending-k6-with-xk6' ---- \ No newline at end of file +--- + +Traditionally, extending k6 with custom functionality that isn't available in the +open source tool has been possible in one of two ways: + +- By importing a JavaScript library. This is a simple way to extend the functionality + of a test script, though it has some drawbacks. Since JavaScript in k6 runs in a + virtual machine [that is unlike the one used in web browsers and + NodeJS](/#what-k6-does-not), it's not possible to use libraries that write to + disk, use JS APIs other than the ones built into k6, or need some new binary + protocol support. Furthermore, while k6 performs well in most use cases, all + execution is interpreted by the [Go JavaScript runtime](https://github.com/dop251/goja), + which can impact the performance of certain resource intensive operations + (cryptography, compression, etc.). + +- By forking the [k6 repository](https://github.com/k6io/k6), doing the changes in Go + and submitting a pull request for review by the core team (preferably following + the [contribution guidelines](https://github.com/k6io/k6/blob/master/CONTRIBUTING.md)). + This is a great way to contribute new core features, but it can be a lengthy + process, and submissions might be rejected if they don't align with the long-term + vision of the project. + +To address these issues and allow the community to more easily adapt k6 to fit their +needs, [k6 v0.29.0](https://github.com/k6io/k6/releases/tag/v0.29.0) introduced +the [xk6 framework](https://github.com/k6io/xk6) and the concept of k6 extensions. + + +## What are k6 extensions? + +k6 extensions are standalone Go projects that call k6 APIs but are otherwise +unrestricted in their functionality. This provides the freedom for extension authors +to experiment with novel integrations with k6 that could eventually easily become +part of core. In this sense extensions can be thought of as a "testing ground" for +eventual promotion upstream, once their features and API is stable, and given that +the extension is generally useful for all k6 users. + + +## What is xk6? + +[xk6](https://github.com/k6io/xk6) is a command-line tool and framework inspired by +[xcaddy](https://github.com/caddyserver/xcaddy), designed for building custom k6 +binaries that bundle one or more extensions written in Go. + +Its main features are: + +- Ease of use: with a few commands even less technical users should be able to build + their own k6 binaries, given that they have the Go toolchain installed and any + dependencies required by a specific extension. +- Simple API for Go programmers that handles the Go<->JS translation, with the + ability to call any public k6 Go API. Extensions are first-class components along + with other built-in modules. +- Cross-platform like Go and runs great on macOS, Windows and Linux. + + +### Extension types + +The initial version of xk6 released in v0.29.0 supported only JavaScript extensions, +but since then we've added support for Output extensions, and are considering +expanding this to other areas of k6 as well. + +The currently supported extension types are: + +#### JavaScript extension + +These extensions enhance the k6 JavaScript API to add support for new network +protocols, achieve better performance than equivalent JS libraries, or implement +features that are unlikely to be made part of the k6 core. + +Some examples include: [xk6-sql](https://github.com/imiric/xk6-sql), +[xk6-crypto](https://github.com/szkiba/xk6-crypto) and [xk6-file](https://github.com/avitalique/xk6-file). + + +#### Output extension + +While k6 has built-in support for many popular +[output backends](/docs/getting-started/results-output/), this list will undoubtedly +not be exhaustive. Output extensions receive metric samples from k6, and are able to +do any processing or further dispatching. + +Some examples include: [xk6-prometheus](https://github.com/szkiba/xk6-prometheus) +and [xk6-influxdbv2](https://github.com/li-zhixin/xk6-influxdbv2). + + +## How to get started + +There are two ways that xk6 can be used: + +- By k6 users that wish to enhance their tests with existing extensions. A + familiarity with the command line and Go is preferred, but not required. + +- By Go developers interested in creating their own k6 extension. They'll need to be + familiar with both Go and JavaScript, understand how the k6 Go<->JS bridge works, + and maintain a public repository for the extension that keeps up to date with any + breaking API changes while xk6 is being stabilized. + + +### Using xk6 to build a k6 binary + +You might have found a neat k6 extension on the [Ecosystem page](/ecosystem) or on +[GitHub](https://github.com/topics/xk6) and wish to use it in your tests. Great! The +process is relatively simple and we're working on simplifying it further for end +users. + +You'll first need to setup [Go](https://golang.org/doc/install) and +[Git](https://git-scm.com/). Make sure that your `$PATH` environment variable is +updated and that `go version` returns the correct version. + +Then install xk6 with: + + + +```bash +$ go install github.com/k6io/xk6/cmd/xk6@latest +``` + + + +And confirm that `which xk6` on Linux/macOS or `where xk6.exe` on Windows returns a +valid path. Otherwise ensure that `$GOPATH` is correctly defined and that +`$GOPATH/bin` is added to your `$PATH` environment variable. See the +[Go documentation](https://golang.org/cmd/go/#hdr-GOPATH_environment_variable) for details. + +Once xk6 is installed, building a k6 binary with one or more extensions can be done +with the following command: + + + +```bash +$ xk6 build v0.33.0 \ + --with github.com/imiric/xk6-sql \ + --with github.com/szkiba/xk6-prometheus +``` + + + +This will build a `k6` binary in the current directory based on k6 v0.33.0, bundling +a JavaScript and an Output extension. Now you can run a script with this binary that +uses the [`xk6-sql` JS API](https://github.com/imiric/xk6-sql) and the +[Prometheus output](https://github.com/szkiba/xk6-prometheus). + +Note that you'll need to specify the binary in the current directory (i.e. `./k6` or +`.\k6` on Windows) to avoid using any other `k6` binary in your `PATH`. + + +### Writing a new extension + +A good starting point for using xk6 and writing your own extension is the [xk6 +introductory article](https://k6.io/blog/extending-k6-with-xk6). + + +You'll first need to decide the type of extension you wish to create. Choose a +JavaScript extension if you want to extend the JS functionality of your script, or an +Output extension if you want to process the metrics emitted by k6 in some way. + +A few things to keep in mind when writing a new extension: + + +- JS extensions are registered using + [`modules.Register()`](https://pkg.go.dev/go.k6.io/k6@v0.33.0/js/modules#Register) + and Output extensions using + [`output.RegisterExtension()`](https://pkg.go.dev/go.k6.io/k6@v0.33.0/output#RegisterExtension), + which should be called in the `init()` function of the module. + In either case you'll have to specify a unique name that preferably doesn't clash + with any existing extension. JS extensions also must begin with `k6/x/` so as to + differentiate them from built-in JS modules. + +- JS extensions expose Go methods which can optionally receive a + [`context.Context`](https://golang.org/pkg/context/#Context) instance as the first argument. + This context is used throughout the k6 codebase and contains embedded objects + which can be extracted to access the + [`goja.Runtime`](https://pkg.go.dev/github.com/dop251/goja#Runtime) for a + particular VU, and to get more information about the test in progress. + Take a look at + [`common.GetRuntime()`](https://pkg.go.dev/go.k6.io/k6@v0.33.0/js/common#GetRuntime), + [`lib.GetState()`](https://pkg.go.dev/go.k6.io/k6@v0.33.0/lib#GetState) and + [`lib.GetExecutionState()`](https://pkg.go.dev/go.k6.io/k6@v0.33.0/lib#GetExecutionState). + +- Output extensions must implement the + [`output.Output`](https://pkg.go.dev/go.k6.io/k6@v0.33.0/output#Output) interface. + They should be particularly weary of performance, since they can receive a large + amount of metrics to process, which can easily degrade the performance of the test + if the extension is inefficient. + As such you should ensure that `AddMetricSamples()` doesn't block for a long time, + and that metrics are flushed periodically to avoid memory leaks. + + Since this is common functionality that most outputs should have, we've provided + a couple of helper structs: [`output.SampleBuffer`](https://pkg.go.dev/go.k6.io/k6@v0.33.0/output#SampleBuffer) + and [`output.PeriodicFlusher`](https://pkg.go.dev/go.k6.io/k6@v0.33.0/output#PeriodicFlusher) + that output implementations can use which handles bufferring and periodic + flushing in a thread-safe way. + For usage examples see the [`statsd` output](https://github.com/k6io/k6/blob/v0.33.0/output/statsd/output.go#L55). + +- Custom metric emission can be done by creating new metrics using [`stats.New()`](https://pkg.go.dev/go.k6.io/k6@v0.33.0/stats#New) + and emitting them using [`stats.PushIfNotDone()`](https://pkg.go.dev/go.k6.io/k6@v0.33.0/stats#PushIfNotDone). + For an example of this see the [`xk6-remote-write` extension](https://github.com/dgzlopes/xk6-remote-write). From 8854c23415170ff9e4fc5c489b254c063430db53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Miri=C4=87?= Date: Mon, 4 Oct 2021 12:35:08 +0200 Subject: [PATCH 02/14] Add JS and Output sections Resolves https://github.com/grafana/k6-docs/pull/346#discussion_r662003317 --- .../en/07 Misc/05 k6 Extensions.md | 313 ++++++++++++++---- 1 file changed, 251 insertions(+), 62 deletions(-) diff --git a/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md b/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md index b3565b3708..4492bfd421 100644 --- a/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md +++ b/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md @@ -74,18 +74,19 @@ Some examples include: [xk6-sql](https://github.com/imiric/xk6-sql), #### Output extension -While k6 has built-in support for many popular -[output backends](/docs/getting-started/results-output/), this list will undoubtedly -not be exhaustive. Output extensions receive metric samples from k6, and are able to -do any processing or further dispatching. +While k6 has built-in support for many popular [output +backends](/docs/getting-started/results-output/), this list will undoubtedly not be +exhaustive. Support for new systems and novel ways of handling the metric data +generated by k6 can be easily added with Output extensions. These receive metric +samples from k6, and are able to do any processing or further dispatching. Some examples include: [xk6-prometheus](https://github.com/szkiba/xk6-prometheus) and [xk6-influxdbv2](https://github.com/li-zhixin/xk6-influxdbv2). -## How to get started +## Getting started -There are two ways that xk6 can be used: +There are two ways in which xk6 can be used: - By k6 users that wish to enhance their tests with existing extensions. A familiarity with the command line and Go is preferred, but not required. @@ -95,8 +96,10 @@ There are two ways that xk6 can be used: and maintain a public repository for the extension that keeps up to date with any breaking API changes while xk6 is being stabilized. - -### Using xk6 to build a k6 binary + + + +## Using xk6 to build a k6 binary You might have found a neat k6 extension on the [Ecosystem page](/ecosystem) or on [GitHub](https://github.com/topics/xk6) and wish to use it in your tests. Great! The @@ -117,7 +120,7 @@ $ go install github.com/k6io/xk6/cmd/xk6@latest -And confirm that `which xk6` on Linux/macOS or `where xk6.exe` on Windows returns a +And confirm that `which xk6` on Linux/macOS or `where xk6` on Windows returns a valid path. Otherwise ensure that `$GOPATH` is correctly defined and that `$GOPATH/bin` is added to your `$PATH` environment variable. See the [Go documentation](https://golang.org/cmd/go/#hdr-GOPATH_environment_variable) for details. @@ -140,58 +143,244 @@ a JavaScript and an Output extension. Now you can run a script with this binary uses the [`xk6-sql` JS API](https://github.com/imiric/xk6-sql) and the [Prometheus output](https://github.com/szkiba/xk6-prometheus). -Note that you'll need to specify the binary in the current directory (i.e. `./k6` or -`.\k6` on Windows) to avoid using any other `k6` binary in your `PATH`. - - -### Writing a new extension - -A good starting point for using xk6 and writing your own extension is the [xk6 -introductory article](https://k6.io/blog/extending-k6-with-xk6). - - -You'll first need to decide the type of extension you wish to create. Choose a -JavaScript extension if you want to extend the JS functionality of your script, or an -Output extension if you want to process the metrics emitted by k6 in some way. - -A few things to keep in mind when writing a new extension: - - -- JS extensions are registered using - [`modules.Register()`](https://pkg.go.dev/go.k6.io/k6@v0.33.0/js/modules#Register) - and Output extensions using - [`output.RegisterExtension()`](https://pkg.go.dev/go.k6.io/k6@v0.33.0/output#RegisterExtension), - which should be called in the `init()` function of the module. - In either case you'll have to specify a unique name that preferably doesn't clash - with any existing extension. JS extensions also must begin with `k6/x/` so as to - differentiate them from built-in JS modules. - -- JS extensions expose Go methods which can optionally receive a - [`context.Context`](https://golang.org/pkg/context/#Context) instance as the first argument. - This context is used throughout the k6 codebase and contains embedded objects - which can be extracted to access the - [`goja.Runtime`](https://pkg.go.dev/github.com/dop251/goja#Runtime) for a - particular VU, and to get more information about the test in progress. - Take a look at - [`common.GetRuntime()`](https://pkg.go.dev/go.k6.io/k6@v0.33.0/js/common#GetRuntime), - [`lib.GetState()`](https://pkg.go.dev/go.k6.io/k6@v0.33.0/lib#GetState) and - [`lib.GetExecutionState()`](https://pkg.go.dev/go.k6.io/k6@v0.33.0/lib#GetExecutionState). - -- Output extensions must implement the - [`output.Output`](https://pkg.go.dev/go.k6.io/k6@v0.33.0/output#Output) interface. - They should be particularly weary of performance, since they can receive a large - amount of metrics to process, which can easily degrade the performance of the test - if the extension is inefficient. - As such you should ensure that `AddMetricSamples()` doesn't block for a long time, - and that metrics are flushed periodically to avoid memory leaks. - - Since this is common functionality that most outputs should have, we've provided - a couple of helper structs: [`output.SampleBuffer`](https://pkg.go.dev/go.k6.io/k6@v0.33.0/output#SampleBuffer) - and [`output.PeriodicFlusher`](https://pkg.go.dev/go.k6.io/k6@v0.33.0/output#PeriodicFlusher) - that output implementations can use which handles bufferring and periodic - flushing in a thread-safe way. - For usage examples see the [`statsd` output](https://github.com/k6io/k6/blob/v0.33.0/output/statsd/output.go#L55). +Note that when running the script we have to specify the binary just built in the +current directory (`./k6`), as otherwise some other `k6` binary found on the system +could be executed which might not have the extensions built-in. This is only the case +on Linux and macOS, as Windows shells will execute the binary in the current +directory first. + + +## Writing a new extension + +The first thing you should do before starting work on a new extension is to confirm +that a similar extension doesn't already exist for your use case. Take a look at +the [Ecosystem page](/ecosystem) and the [`xk6` topic on GitHub](https://github.com/topics/xk6). +For example, if a system you need support for can be tested with a generic protocol +like MQTT, prefer using [xk6-mqtt](https://github.com/pmalhaire/xk6-mqtt) +instead of creating an extension that uses some custom protocol. + +Next, you should decide the type of extension you need. A JavaScript extension is a +good fit if you want to extend the JS functionality of your script, or add support +for a new network protocol to test with. An Output extension would be more suitable +if you need to process the metrics emitted by k6 in some way, submit them to a +specific storage backend that was previously unsupported, etc. The k6 APIs you'll +need to use and things to consider while developing will be different in each case. + + +### Writing a new JavaScript extension + +A good starting point for using xk6 and writing a JS extension is the [xk6 +introductory article](https://k6.io/blog/extending-k6-with-xk6), but we'll cover +some of the details here. + +JavaScript extensions consist of a main module struct that exposes methods that +can be called from a k6 test script. For example: + + + + + +```go +package compare + +type Compare struct{} + +func (*Compare) IsGreater(int a, b) bool { + return a > b +} +``` + + + +In order to use this from k6 test scripts we need to register the module +by adding the following: + + + +```go +import "go.k6.io/k6/js/modules" + +func init() { + modules.Register("k6/x/compare", new(Compare)) +} +``` + + + +Note that all k6 extensions should have the `k6/x/` prefix and the short name +must be unique among all extensions built in the same k6 binary. + +We can then build a k6 binary with this extension by running +`xk6 build --with xk6-compare=.`. In this case `xk6-compare` is the +Go module name passed to `go mod init`, but in a real-world scenario +this would be a URL. -- Custom metric emission can be done by creating new metrics using [`stats.New()`](https://pkg.go.dev/go.k6.io/k6@v0.33.0/stats#New) - and emitting them using [`stats.PushIfNotDone()`](https://pkg.go.dev/go.k6.io/k6@v0.33.0/stats#PushIfNotDone). +Finally we can use the extension in a test script: + + + +```go +import compare from 'k6/x/compare'; + +export default function () { + console.log(compare.isGreater(2, 1)); +} +``` + + + +And run the test with `./k6 run test.js`, which should output `INFO[0000] true`. + + +#### Additional features + +The k6 Go-JS bridge has a few features we should highlight: + +- Go method names will be converted from Pascal case to Camel case when + accessed in JS, as in the example above: `IsGreater` becomes `isGreater`. + + +- Similarly, Go field names will be converted from Pascal case to Snake case. + For example, the struct field `SomeField string` is accessible in JS + as the `some_field` object property. This behavior is configurable with the + `js` struct tag, so this can be changed with `SomeField string ``js:"someField"``` + or the field can be hidden with `js:"-"`. + +- Method names prefixed with `X` are interpreted as constructors in JS, + and will support the `new` operator. + For example, defining the following method on the above struct: + + + +```go +func (*Compare) XComparator() *Comparator { + return &Comparator{} +} +``` + + + + Would allow creating a `Comparator` instance in JS with `new compare.Comparator()`, + which is a bit more idiomatic to JS. + +- Methods that specify `context.Context` or `*context.Context` as the first + argument will be passed the `Context` instance used internally in k6, + which has attached some useful objects for inspecting the internal execution + state, such as + [`lib.State`](https://github.com/grafana/k6/blob/v0.33.0/lib/state.go#L43) + or VU state, [`lib.ExecutionState`](https://github.com/grafana/k6/blob/v0.33.0/lib/execution.go#L142), + and the [`goja.Runtime`](https://github.com/dop251/goja/blob/705acef95ba3654f89c969d9e792ac5f49215350/runtime.go#L162) instance + the VU is using to execute the script. + This feature is used extensively in the + [`xk6-execution`](https://github.com/grafana/xk6-execution) extension. + + + + +#### Things to keep in mind + +- The code in the `default` function (or another function specified by + [`exec`](/using-k6/scenarios/#common-options)) will be executed many + times during a test run and possibly in parallel by thousands of VUs. + As such any operation of your extension meant to run in that context + needs to be performant and [thread-safe](https://en.wikipedia.org/wiki/Thread_safety). +- Any heavy initialization should be done in the [`init` + context](/javascript-api/init-context/)... +- Custom metric emission can be done by creating new metrics using + [`stats.New()`](https://github.com/grafana/k6/blob/v0.33.0/stats/stats.go#L449) + and emitting them using [`stats.PushIfNotDone()`](https://github.com/grafana/k6/blob/v0.33.0/stats/stats.go#L429). For an example of this see the [`xk6-remote-write` extension](https://github.com/dgzlopes/xk6-remote-write). + + +### Writing a new Output extension + +Output extensions are similarly written, but have a different API and performance +considerations. + +The core of an Output extension is a struct that implements the [`output.Output` +interface](https://github.com/grafana/k6/blob/v0.33.0/output/types.go#L57). For example: + + + +```go +package log + +import ( + "fmt" + "io" + + "go.k6.io/k6/output" + "go.k6.io/k6/stats" +) + +// Register the extension on module initialization. +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 and prints them to stdout. +func (l *Logger) AddMetricSamples(samples []stats.SampleContainer) { + for i := range samples { + all := samples[i].GetSamples() + for j := range all { + fmt.Fprintf(l.out, "%d %s: %f\n", all[j].Time.UnixNano(), all[j].Metric.Name, all[j].Value) + } + } +} + +// 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 of + [`output.Params`](https://github.com/grafana/k6/blob/v0.33.0/output/types.go#L36). + 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 simply writes to stdout. In a real-world + scenario this output might have to be buffered and flushed periodically to avoid + memory leaks. Below we'll discuss some helpers you can use for that. + + +#### Additional features + +- Output structs can optionally implement additional interfaces that allows them to + [receive thresholds](https://github.com/grafana/k6/blob/v0.33.0/output/types.go#L79), + [test run status updates](https://github.com/grafana/k6/blob/v0.33.0/output/types.go#L94) + or [interrupt a test run](https://github.com/grafana/k6/blob/v0.33.0/output/types.go#L88). +- Because output implementations typically need to process large amounts of data that + k6 produces and dispatch it to another system, we've provided a couple of helper + structs you can use in your extensions: + [`output.SampleBuffer`](https://github.com/grafana/k6/blob/v0.33.0/output/helpers.go#L35) + is a thread-safe buffer for metric samples to help with memory management and + [`output.PeriodicFlusher`](https://github.com/grafana/k6/blob/v0.33.0/output/helpers.go#L75) + will periodically run a function which is useful for flushing or dispatching the + buffered samples. + For usage examples see the [`statsd` output](https://github.com/k6io/k6/blob/v0.33.0/output/statsd/output.go#L55). From f7209504e4462da4ba8ffa28c4ab16e4e8ac35f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Miri=C4=87?= Date: Mon, 4 Oct 2021 12:35:11 +0200 Subject: [PATCH 03/14] Add version support notice to xk6 docs Partly resolves https://github.com/grafana/k6-docs/pull/346/files#r661994675 --- .../markdown/translated-guides/en/07 Misc/05 k6 Extensions.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md b/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md index 4492bfd421..09b2bb9424 100644 --- a/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md +++ b/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md @@ -2,6 +2,8 @@ title: 'k6 Extensions' --- +> *Since v0.29.0 (JavaScript extensions) and v0.31.0 (Output extensions)* + Traditionally, extending k6 with custom functionality that isn't available in the open source tool has been possible in one of two ways: From 75d954706145393bae547a245843003bfa2661b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Miri=C4=87?= Date: Mon, 4 Oct 2021 12:35:14 +0200 Subject: [PATCH 04/14] Mention supported modes of execution --- .../translated-guides/en/07 Misc/05 k6 Extensions.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md b/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md index 09b2bb9424..3299796794 100644 --- a/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md +++ b/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md @@ -386,3 +386,15 @@ Notice a couple of things: will periodically run a function which is useful for flushing or dispatching the buffered samples. For usage examples see the [`statsd` output](https://github.com/k6io/k6/blob/v0.33.0/output/statsd/output.go#L55). + + +## Supported modes of execution + +xk6 and the examples above work great for local execution, i.e. running a +script with a binary built by xk6 on your own infrastructure. Custom k6 binaries are +also supported by the [k6 Kubernetes operator](https://github.com/grafana/k6-operator#using-extensions). + +They're currently not supported in [k6 Cloud](https://k6.io/docs/cloud/), but adding support +is on our roadmap. However, it is possible to run a binary built with xk6 and send +metrics produced by extension code to the Cloud using the +[`cloud` output](https://k6.io/docs/results-visualization/cloud/). From 5fe9242be48b87854d4f63ca890dcc063eb016a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Miri=C4=87?= Date: Mon, 4 Oct 2021 12:35:17 +0200 Subject: [PATCH 05/14] Replace GitHub code links with pkg.go.dev https://github.com/grafana/k6-docs/pull/346#discussion_r719252519 --- .../en/07 Misc/05 k6 Extensions.md | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md b/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md index 3299796794..c47ca190db 100644 --- a/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md +++ b/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md @@ -269,9 +269,9 @@ func (*Compare) XComparator() *Comparator { argument will be passed the `Context` instance used internally in k6, which has attached some useful objects for inspecting the internal execution state, such as - [`lib.State`](https://github.com/grafana/k6/blob/v0.33.0/lib/state.go#L43) - or VU state, [`lib.ExecutionState`](https://github.com/grafana/k6/blob/v0.33.0/lib/execution.go#L142), - and the [`goja.Runtime`](https://github.com/dop251/goja/blob/705acef95ba3654f89c969d9e792ac5f49215350/runtime.go#L162) instance + [`lib.State`](https://pkg.go.dev/go.k6.io/k6/lib#State) + or VU state, [`lib.ExecutionState`](https://pkg.go.dev/go.k6.io/k6/lib#ExecutionState), + and the [`goja.Runtime`](https://pkg.go.dev/github.com/dop251/goja#Runtime) instance the VU is using to execute the script. This feature is used extensively in the [`xk6-execution`](https://github.com/grafana/xk6-execution) extension. @@ -289,8 +289,8 @@ func (*Compare) XComparator() *Comparator { - Any heavy initialization should be done in the [`init` context](/javascript-api/init-context/)... - Custom metric emission can be done by creating new metrics using - [`stats.New()`](https://github.com/grafana/k6/blob/v0.33.0/stats/stats.go#L449) - and emitting them using [`stats.PushIfNotDone()`](https://github.com/grafana/k6/blob/v0.33.0/stats/stats.go#L429). + [`stats.New()`](https://pkg.go.dev/go.k6.io/k6/stats#New) + and emitting them using [`stats.PushIfNotDone()`](https://pkg.go.dev/go.k6.io/k6/stats#PushIfNotDone). For an example of this see the [`xk6-remote-write` extension](https://github.com/dgzlopes/xk6-remote-write). @@ -300,7 +300,7 @@ Output extensions are similarly written, but have a different API and performanc considerations. The core of an Output extension is a struct that implements the [`output.Output` -interface](https://github.com/grafana/k6/blob/v0.33.0/output/types.go#L57). For example: +interface](https://pkg.go.dev/go.k6.io/k6/output#Output). For example: @@ -363,7 +363,7 @@ func (*Logger) Stop() error { Notice a couple of things: - The module initializer `New()` receives an instance of - [`output.Params`](https://github.com/grafana/k6/blob/v0.33.0/output/types.go#L36). + [`output.Params`](https://pkg.go.dev/go.k6.io/k6/output#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 simply writes to stdout. In a real-world @@ -374,18 +374,18 @@ Notice a couple of things: #### Additional features - Output structs can optionally implement additional interfaces that allows them to - [receive thresholds](https://github.com/grafana/k6/blob/v0.33.0/output/types.go#L79), - [test run status updates](https://github.com/grafana/k6/blob/v0.33.0/output/types.go#L94) - or [interrupt a test run](https://github.com/grafana/k6/blob/v0.33.0/output/types.go#L88). + [receive thresholds](https://pkg.go.dev/go.k6.io/k6/output#WithThresholds), + [test run status updates](https://pkg.go.dev/go.k6.io/k6/output#WithRunStatusUpdates) + or [interrupt a test run](https://pkg.go.dev/go.k6.io/k6/output#WithTestRunStop). - Because output implementations typically need to process large amounts of data that k6 produces and dispatch it to another system, we've provided a couple of helper structs you can use in your extensions: - [`output.SampleBuffer`](https://github.com/grafana/k6/blob/v0.33.0/output/helpers.go#L35) + [`output.SampleBuffer`](https://pkg.go.dev/go.k6.io/k6/output#SampleBuffer) is a thread-safe buffer for metric samples to help with memory management and - [`output.PeriodicFlusher`](https://github.com/grafana/k6/blob/v0.33.0/output/helpers.go#L75) + [`output.PeriodicFlusher`](https://pkg.go.dev/go.k6.io/k6/output#PeriodicFlusher) will periodically run a function which is useful for flushing or dispatching the buffered samples. - For usage examples see the [`statsd` output](https://github.com/k6io/k6/blob/v0.33.0/output/statsd/output.go#L55). + For usage examples see the [`statsd` output](https://pkg.go.dev/go.k6.io/k6/output/statsd). ## Supported modes of execution From 461cba18272d87772333ec428d9d5024bdc7ff46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Miri=C4=87?= Date: Mon, 4 Oct 2021 12:35:25 +0200 Subject: [PATCH 06/14] Suggest implementing a JS library instead of an extension Resolves https://github.com/grafana/k6-docs/pull/346#discussion_r677584294 --- .../markdown/translated-guides/en/07 Misc/05 k6 Extensions.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md b/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md index c47ca190db..5adb1ab22d 100644 --- a/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md +++ b/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md @@ -160,6 +160,9 @@ the [Ecosystem page](/ecosystem) and the [`xk6` topic on GitHub](https://github. For example, if a system you need support for can be tested with a generic protocol like MQTT, prefer using [xk6-mqtt](https://github.com/pmalhaire/xk6-mqtt) instead of creating an extension that uses some custom protocol. +Also, prefer to write a pure JavaScript library that can be used in k6 if you can +avoid writing an extension in Go, since a JS library will be better supported and +likely easier to write and reuse than an extension. Next, you should decide the type of extension you need. A JavaScript extension is a good fit if you want to extend the JS functionality of your script, or add support From 3311325b37e891c53ea5e413b894341859be502a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Miri=C4=87?= Date: Mon, 4 Oct 2021 12:05:06 +0200 Subject: [PATCH 07/14] Don't mention release versions again Resolves: - https://github.com/grafana/k6-docs/pull/346#discussion_r678081488 - https://github.com/grafana/k6-docs/pull/346#discussion_r678081893 --- .../translated-guides/en/07 Misc/05 k6 Extensions.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md b/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md index 5adb1ab22d..8116b879a5 100644 --- a/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md +++ b/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md @@ -25,8 +25,8 @@ open source tool has been possible in one of two ways: vision of the project. To address these issues and allow the community to more easily adapt k6 to fit their -needs, [k6 v0.29.0](https://github.com/k6io/k6/releases/tag/v0.29.0) introduced -the [xk6 framework](https://github.com/k6io/xk6) and the concept of k6 extensions. +needs, we released the [xk6 framework](https://github.com/k6io/xk6) and the concept +of k6 extensions. ## What are k6 extensions? @@ -58,9 +58,9 @@ Its main features are: ### Extension types -The initial version of xk6 released in v0.29.0 supported only JavaScript extensions, -but since then we've added support for Output extensions, and are considering -expanding this to other areas of k6 as well. +The initial version of xk6 supported only JavaScript extensions, but since then we've +added support for Output extensions, and are considering expanding this to other +areas of k6 as well. The currently supported extension types are: From 9dbf5cade01c0c095435445c2f77c1af8f990e12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Miri=C4=87?= Date: Mon, 4 Oct 2021 12:34:32 +0200 Subject: [PATCH 08/14] Address comments by codebien --- .../en/07 Misc/05 k6 Extensions.md | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md b/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md index 8116b879a5..77c49c854a 100644 --- a/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md +++ b/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md @@ -82,8 +82,8 @@ exhaustive. Support for new systems and novel ways of handling the metric data generated by k6 can be easily added with Output extensions. These receive metric samples from k6, and are able to do any processing or further dispatching. -Some examples include: [xk6-prometheus](https://github.com/szkiba/xk6-prometheus) -and [xk6-influxdbv2](https://github.com/li-zhixin/xk6-influxdbv2). +Some examples include: [xk6-output-kafka](https://github.com/grafana/xk6-output-kafka) +and [xk6-prometheus](https://github.com/szkiba/xk6-prometheus). ## Getting started @@ -133,17 +133,17 @@ with the following command: ```bash -$ xk6 build v0.33.0 \ +$ xk6 build latest \ --with github.com/imiric/xk6-sql \ --with github.com/szkiba/xk6-prometheus ``` -This will build a `k6` binary in the current directory based on k6 v0.33.0, bundling -a JavaScript and an Output extension. Now you can run a script with this binary that -uses the [`xk6-sql` JS API](https://github.com/imiric/xk6-sql) and the -[Prometheus output](https://github.com/szkiba/xk6-prometheus). +This will build a `k6` binary in the current directory based on the most recently +released k6 version, bundling a JavaScript and an Output extension. Now you can run a +script with this binary that uses the [SQL JS API](https://github.com/imiric/xk6-sql) +and the [Prometheus output](https://github.com/szkiba/xk6-prometheus). Note that when running the script we have to specify the binary just built in the current directory (`./k6`), as otherwise some other `k6` binary found on the system @@ -248,7 +248,7 @@ The k6 Go-JS bridge has a few features we should highlight: - Similarly, Go field names will be converted from Pascal case to Snake case. For example, the struct field `SomeField string` is accessible in JS as the `some_field` object property. This behavior is configurable with the - `js` struct tag, so this can be changed with `SomeField string ``js:"someField"``` + `js` struct tag, so this can be changed with SomeField string `js:"someField"` or the field can be hidden with `js:"-"`. - Method names prefixed with `X` are interpreted as constructors in JS, @@ -289,8 +289,9 @@ func (*Compare) XComparator() *Comparator { times during a test run and possibly in parallel by thousands of VUs. As such any operation of your extension meant to run in that context needs to be performant and [thread-safe](https://en.wikipedia.org/wiki/Thread_safety). -- Any heavy initialization should be done in the [`init` - context](/javascript-api/init-context/)... +- Any heavy initialization should be done in the [init + context](/javascript-api/init-context/) if possible, and not as part of the + `default` function execution. - Custom metric emission can be done by creating new metrics using [`stats.New()`](https://pkg.go.dev/go.k6.io/k6/stats#New) and emitting them using [`stats.PushIfNotDone()`](https://pkg.go.dev/go.k6.io/k6/stats#PushIfNotDone). From 141057f1ca3a6495dab1adb355fbe1c4273ffeb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Miri=C4=87?= Date: Wed, 6 Oct 2021 18:31:56 +0200 Subject: [PATCH 09/14] Add advanced JS module example This briefly mentions common.Bind() and shows a more advanced module example using the new IsModuleV2 approach. Should resolve https://github.com/grafana/k6-docs/pull/346#discussion_r719253757 --- .../en/07 Misc/05 k6 Extensions.md | 256 +++++++++++++++--- 1 file changed, 225 insertions(+), 31 deletions(-) diff --git a/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md b/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md index 77c49c854a..6c9fd08b49 100644 --- a/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md +++ b/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md @@ -174,23 +174,19 @@ need to use and things to consider while developing will be different in each ca ### Writing a new JavaScript extension -A good starting point for using xk6 and writing a JS extension is the [xk6 -introductory article](https://k6.io/blog/extending-k6-with-xk6), but we'll cover -some of the details here. - -JavaScript extensions consist of a main module struct that exposes methods that -can be called from a k6 test script. For example: +A simple JavaScript extension consists of a main module struct that exposes +methods that can be called from a k6 test script. For example: - + ```go package compare type Compare struct{} -func (*Compare) IsGreater(int a, b) bool { +func (*Compare) IsGreater(a, b int) bool { return a > b } ``` @@ -200,7 +196,7 @@ func (*Compare) IsGreater(int a, b) bool { In order to use this from k6 test scripts we need to register the module by adding the following: - + ```go import "go.k6.io/k6/js/modules" @@ -215,6 +211,28 @@ func init() { Note that all k6 extensions should have the `k6/x/` prefix and the short name must be unique among all extensions built in the same k6 binary. +The final extension code will look like so: + + + +```go +package compare + +import "go.k6.io/k6/js/modules" + +func init() { + modules.Register("k6/x/compare", new(Compare)) +} + +type Compare struct{} + +func (*Compare) IsGreater(a, b int) bool { + return a > b +} +``` + + + We can then build a k6 binary with this extension by running `xk6 build --with xk6-compare=.`. In this case `xk6-compare` is the Go module name passed to `go mod init`, but in a real-world scenario @@ -224,7 +242,7 @@ Finally we can use the extension in a test script: -```go +```javascript import compare from 'k6/x/compare'; export default function () { @@ -237,27 +255,29 @@ export default function () { And run the test with `./k6 run test.js`, which should output `INFO[0000] true`. -#### Additional features +#### Notable features The k6 Go-JS bridge has a few features we should highlight: - Go method names will be converted from Pascal case to Camel case when accessed in JS, as in the example above: `IsGreater` becomes `isGreater`. - - Similarly, Go field names will be converted from Pascal case to Snake case. - For example, the struct field `SomeField string` is accessible in JS - as the `some_field` object property. This behavior is configurable with the - `js` struct tag, so this can be changed with SomeField string `js:"someField"` + For example, the struct field `SomeField string` will be accessible in JS as + the `some_field` object property. This behavior is configurable with the `js` + struct tag, so this can be changed with + SomeField string `js:"someField"` or the field can be hidden with `js:"-"`. -- Method names prefixed with `X` are interpreted as constructors in JS, - and will support the `new` operator. +- Methods with a name is prefixed with `X` will be transformed to JS + constructors, and will support the `new` operator. For example, defining the following method on the above struct: - + ```go +type Comparator struct{} + func (*Compare) XComparator() *Comparator { return &Comparator{} } @@ -268,18 +288,192 @@ func (*Compare) XComparator() *Comparator { Would allow creating a `Comparator` instance in JS with `new compare.Comparator()`, which is a bit more idiomatic to JS. -- Methods that specify `context.Context` or `*context.Context` as the first - argument will be passed the `Context` instance used internally in k6, - which has attached some useful objects for inspecting the internal execution - state, such as - [`lib.State`](https://pkg.go.dev/go.k6.io/k6/lib#State) - or VU state, [`lib.ExecutionState`](https://pkg.go.dev/go.k6.io/k6/lib#ExecutionState), - and the [`goja.Runtime`](https://pkg.go.dev/github.com/dop251/goja#Runtime) instance - the VU is using to execute the script. - This feature is used extensively in the - [`xk6-execution`](https://github.com/grafana/xk6-execution) extension. - +#### Advanced JavaScript extension + +> ℹ️ **Note** +> +> The internal JavaScript module API is currently (October 2021) in a state of +> flux. The traditional approach of initializing JS modules involves calling +> [`common.Bind()`](https://pkg.go.dev/go.k6.io/k6@v0.34.1/js/common#Bind) +> on any objects that need to be exposed to JS. This method has a few technical +> issues we want to improve, and also isn't flexible enough to implement +> new features like giving extensions access to internal k6 objects. +> Starting from v0.32.0 we've introduced a new approach for writing +> JS modules and is the method we'll be describing below. While this new API +> is recommended for new modules and extensions, note that it's still in +> development and might change while it's being stabilized. + +If your extension requires access to internal k6 objects to, for example, +inspect the state of the test during execution, we will need to make some +slightly more complicated changes to the above example. + +Our main `Compare` struct should implement the +[`modules.Instance` interface](https://pkg.go.dev/go.k6.io/k6@v0.34.1/js/modules#Instance) +and embed +[`modules.InstanceCore`](https://pkg.go.dev/go.k6.io/k6@v0.34.1/js/modules#InstanceCore) +in order to access internal k6 objects such as: +- [`lib.State`](https://pkg.go.dev/go.k6.io/k6/lib#State): the VU state with + values like the VU ID and iteration number. +- [`goja.Runtime`](https://pkg.go.dev/github.com/dop251/goja#Runtime): the + JavaScript runtime used by the VU. +- a global `context.Context` which contains other interesting objects like + [`lib.ExecutionState`](https://pkg.go.dev/go.k6.io/k6/lib#ExecutionState). + +Additionally there should be a root module implementation of the +[`modules.IsModuleV2` interface](https://pkg.go.dev/go.k6.io/k6@v0.34.1/js/modules#IsModuleV2) +that will serve as a factory of `Compare` instances for each VU. Note that this +can have memory implications depending on the size of your module. + +Here's how that would look like: + + + +```go +package compare + +import "go.k6.io/k6/js/modules" + +func init() { + modules.Register("k6/x/compare", New()) +} + +type ( + // RootModule is the global module instance that will create Compare + // instances for each VU. + RootModule struct{} + + // Compare represents an instance of the JS module. + Compare struct { + // InstanceCore provides some useful methods for accessing internal k6 + // objects like the global context, VU state and goja runtime. + modules.InstanceCore + } +) + +// Ensure the interfaces are implemented correctly. +var ( + _ modules.Instance = &Compare{} + _ modules.IsModuleV2 = &RootModule{} +) + +// New returns a pointer to a new RootModule instance. +func New() *RootModule { + return &RootModule{} +} + +// NewModuleInstance implements the modules.IsModuleV2 interface and returns +// a new instance for each VU. +func (*RootModule) NewModuleInstance(m modules.InstanceCore) modules.Instance { + return &Compare{InstanceCore: m} +} + +// GetExports implements the modules.Instance interface and returns the exports +// of the JS module. +func (c *Compare) GetExports() modules.Exports { + return modules.Exports{Default: c} +} + +// IsGreater returns true if a is greater than b, false otherwise. +func (*Compare) IsGreater(a, b int) bool { + return a > b +} +``` + + + +Currently this module isn't taking advantage of the methods provided by +[`modules.InstanceCore`](https://pkg.go.dev/go.k6.io/k6@v0.34.1/js/modules#InstanceCore) +because our simple example extension doesn't require it, but here is +a contrived example of how that could be done: + + + +```go +type InternalState struct { + ActiveVUs int64 `js:"activeVUs"` + Iteration int64 + VUID uint64 `js:"vuID"` + VUIDFromRuntime goja.Value `js:"vuIDFromRuntime"` +} + +func (c *Compare) GetInternalState() *InternalState { + state := c.GetState() + ctx := c.GetContext() + es := lib.GetExecutionState(ctx) + rt := c.GetRuntime() + + return &InternalState{ + VUID: state.VUID, + VUIDFromRuntime: rt.Get("__VU"), + Iteration: state.Iteration, + ActiveVUs: es.GetCurrentlyActiveVUsCount(), + } +} +``` + + + +Running a script like: + + + +```javascript +import compare from 'k6/x/compare'; + +export default function () { + const state = compare.getInternalState(); + console.log(`Active VUs: ${state.activeVUs} +Iteration: ${state.iteration} +VU ID: ${state.vuID} +VU ID from runtime: ${state.vuIDFromRuntime}`); +} +``` + + + +Should output: + + + +```bash +INFO[0000] Active VUs: 1 +Iteration: 0 +VU ID: 1 +VU ID from runtime: 1 source=console +``` + + + +> ℹ️ **Note** +> +> For a more extensive usage example of this API, take a look at the +> [`k6/execution`](https://github.com/grafana/k6/blob/v0.34.1/js/modules/k6/execution/execution.go) +> module. + +Notice that the JavaScript runtime will transparently convert Go types like +`int64` to their JS equivalent. For complex types where this is not +possible your script might fail with a `TypeError` and you will need to convert +your object to a [`goja.Object`](https://pkg.go.dev/github.com/dop251/goja#Object) or [`goja.Value`](https://pkg.go.dev/github.com/dop251/goja#Value). + +For example: + + + +```go +type Comparator struct{} + +func (*Compare) XComparator(call goja.ConstructorCall, rt *goja.Runtime) *goja.Object { + return rt.ToValue(&Comparator{}).ToObject(rt) +} +``` + + + +This also demonstrates the native constructors feature from goja, where methods +with this signature will be transformed to JS constructors, and also have +the benefit of receiving the `goja.Runtime`, which is an alternative way +to access it in addition to the `GetRuntime()` method shown above. #### Things to keep in mind @@ -300,8 +494,8 @@ func (*Compare) XComparator() *Comparator { ### Writing a new Output extension -Output extensions are similarly written, but have a different API and performance -considerations. +Output extensions are written similarly to JavaScript extensions, but have a +different API and performance considerations. The core of an Output extension is a struct that implements the [`output.Output` interface](https://pkg.go.dev/go.k6.io/k6/output#Output). For example: From 402a03958665acdc32988c50a0ee8c9ca5467cdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Miri=C4=87?= Date: Thu, 7 Oct 2021 12:28:12 +0200 Subject: [PATCH 10/14] Fix exports in advanced JS module example Resolves https://github.com/grafana/k6-docs/pull/346#discussion_r723933014 --- .../en/07 Misc/05 k6 Extensions.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md b/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md index 6c9fd08b49..b64ddd2ad7 100644 --- a/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md +++ b/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md @@ -348,6 +348,8 @@ type ( // InstanceCore provides some useful methods for accessing internal k6 // objects like the global context, VU state and goja runtime. modules.InstanceCore + // Comparator is the exported module instance. + *Comparator } ) @@ -365,18 +367,21 @@ func New() *RootModule { // NewModuleInstance implements the modules.IsModuleV2 interface and returns // a new instance for each VU. func (*RootModule) NewModuleInstance(m modules.InstanceCore) modules.Instance { - return &Compare{InstanceCore: m} + return &Compare{InstanceCore: m, Comparator: &Comparator{}} +} + +// Comparator is the exported module instance. +type Comparator struct{} + +// IsGreater returns true if a is greater than b, or false otherwise. +func (*Comparator) IsGreater(a, b int) bool { + return a > b } // GetExports implements the modules.Instance interface and returns the exports // of the JS module. func (c *Compare) GetExports() modules.Exports { - return modules.Exports{Default: c} -} - -// IsGreater returns true if a is greater than b, false otherwise. -func (*Compare) IsGreater(a, b int) bool { - return a > b + return modules.Exports{Default: c.Comparator} } ``` From c867009d8a2bcc2e4e27bbe61507b71647399235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Miri=C4=87?= Date: Thu, 7 Oct 2021 14:52:09 +0200 Subject: [PATCH 11/14] Fix typo --- .../markdown/translated-guides/en/07 Misc/05 k6 Extensions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md b/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md index b64ddd2ad7..c62a01da01 100644 --- a/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md +++ b/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md @@ -269,7 +269,7 @@ The k6 Go-JS bridge has a few features we should highlight: SomeField string `js:"someField"` or the field can be hidden with `js:"-"`. -- Methods with a name is prefixed with `X` will be transformed to JS +- Methods with a name prefixed with `X` will be transformed to JS constructors, and will support the `new` operator. For example, defining the following method on the above struct: From 4b940cce4c4d4ae2c967968f341102ee2d21c2ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Miri=C4=87?= Date: Thu, 7 Oct 2021 14:52:21 +0200 Subject: [PATCH 12/14] Fix rendering of inline Go struct tag example Resolves https://github.com/grafana/k6-docs/pull/346#discussion_r718619021 --- .../translated-guides/en/07 Misc/05 k6 Extensions.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md b/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md index c62a01da01..426fc34092 100644 --- a/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md +++ b/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md @@ -265,9 +265,9 @@ The k6 Go-JS bridge has a few features we should highlight: - Similarly, Go field names will be converted from Pascal case to Snake case. For example, the struct field `SomeField string` will be accessible in JS as the `some_field` object property. This behavior is configurable with the `js` - struct tag, so this can be changed with - SomeField string `js:"someField"` - or the field can be hidden with `js:"-"`. + struct tag, so this can be changed + with SomeField string `js:"someField"` + or the field can be hidden with `js:"-"`. - Methods with a name prefixed with `X` will be transformed to JS constructors, and will support the `new` operator. From 31164181e960f45ac0014986dcd54f58f831d23b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Miri=C4=87?= Date: Thu, 7 Oct 2021 17:44:44 +0200 Subject: [PATCH 13/14] Remove k6 version from pkg.go.dev links Resolves https://github.com/grafana/k6-docs/pull/346#discussion_r723934615 --- .../translated-guides/en/07 Misc/05 k6 Extensions.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md b/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md index 426fc34092..9458849dcc 100644 --- a/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md +++ b/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md @@ -295,7 +295,7 @@ func (*Compare) XComparator() *Comparator { > > The internal JavaScript module API is currently (October 2021) in a state of > flux. The traditional approach of initializing JS modules involves calling -> [`common.Bind()`](https://pkg.go.dev/go.k6.io/k6@v0.34.1/js/common#Bind) +> [`common.Bind()`](https://pkg.go.dev/go.k6.io/k6/js/common#Bind) > on any objects that need to be exposed to JS. This method has a few technical > issues we want to improve, and also isn't flexible enough to implement > new features like giving extensions access to internal k6 objects. @@ -309,9 +309,9 @@ inspect the state of the test during execution, we will need to make some slightly more complicated changes to the above example. Our main `Compare` struct should implement the -[`modules.Instance` interface](https://pkg.go.dev/go.k6.io/k6@v0.34.1/js/modules#Instance) +[`modules.Instance` interface](https://pkg.go.dev/go.k6.io/k6/js/modules#Instance) and embed -[`modules.InstanceCore`](https://pkg.go.dev/go.k6.io/k6@v0.34.1/js/modules#InstanceCore) +[`modules.InstanceCore`](https://pkg.go.dev/go.k6.io/k6/js/modules#InstanceCore) in order to access internal k6 objects such as: - [`lib.State`](https://pkg.go.dev/go.k6.io/k6/lib#State): the VU state with values like the VU ID and iteration number. @@ -321,7 +321,7 @@ in order to access internal k6 objects such as: [`lib.ExecutionState`](https://pkg.go.dev/go.k6.io/k6/lib#ExecutionState). Additionally there should be a root module implementation of the -[`modules.IsModuleV2` interface](https://pkg.go.dev/go.k6.io/k6@v0.34.1/js/modules#IsModuleV2) +[`modules.IsModuleV2` interface](https://pkg.go.dev/go.k6.io/k6/js/modules#IsModuleV2) that will serve as a factory of `Compare` instances for each VU. Note that this can have memory implications depending on the size of your module. @@ -388,7 +388,7 @@ func (c *Compare) GetExports() modules.Exports { Currently this module isn't taking advantage of the methods provided by -[`modules.InstanceCore`](https://pkg.go.dev/go.k6.io/k6@v0.34.1/js/modules#InstanceCore) +[`modules.InstanceCore`](https://pkg.go.dev/go.k6.io/k6/js/modules#InstanceCore) because our simple example extension doesn't require it, but here is a contrived example of how that could be done: From db1c3511a2573efe6331e4ce88e825369cfd6cdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Miri=C4=87?= Date: Fri, 15 Oct 2021 09:59:47 +0200 Subject: [PATCH 14/14] Add note about vendored dependencies Resolves https://github.com/grafana/k6-docs/pull/346#issuecomment-943561720 --- .../markdown/translated-guides/en/07 Misc/05 k6 Extensions.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md b/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md index 9458849dcc..83b8ba8910 100644 --- a/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md +++ b/src/data/markdown/translated-guides/en/07 Misc/05 k6 Extensions.md @@ -151,6 +151,10 @@ could be executed which might not have the extensions built-in. This is only the on Linux and macOS, as Windows shells will execute the binary in the current directory first. +Also note that because of the way xk6 works, vendored dependencies (the `vendor` +directory created by `go mod vendor`) will **not** be taken into account when +building a binary, and you don't need to commit them to the extension repository. + ## Writing a new extension