Skip to content

Commit

Permalink
Add xk6-redis/websockets/timers as experimental JS modules
Browse files Browse the repository at this point in the history
This commit imports the latest state of xk6-redis, xk6-websockets, and
xk6-timers into k6's core as experimental modules importable under the
"k6/experimental" import path.


Specifically regarding the redis module: with it comes a single direct 
dependency, the `redis/v8` library, and two indirect dependencies
implied by the former `xxhash` and `go-rendezvous`.

This commit's essential files are:
* `js/initcontext.go`: exposes the modules under `k6/experimental`
* `samples/experimental/*.go`: exposes examples of the experimental
module in action
* `samples/docker-compose.yml`: allows to spin up a stack for testing
the redis module

The rest of the files are essentially go module and vendoring plumbing.
  • Loading branch information
oleiade committed Aug 15, 2022
1 parent 4435e87 commit 0b30c35
Show file tree
Hide file tree
Showing 80 changed files with 21,139 additions and 32 deletions.
16 changes: 12 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ require (
github.com/fatih/color v1.13.0
github.com/golang/protobuf v1.5.2
github.com/gorilla/websocket v1.5.0
github.com/grafana/xk6-redis v0.1.1
github.com/grafana/xk6-timers v0.1.0
github.com/grafana/xk6-websockets v0.1.0
github.com/influxdata/influxdb1-client v0.0.0-20190402204710-8ff2fc3824fc
github.com/jhump/protoreflect v1.12.0
github.com/klauspost/compress v1.15.7
Expand All @@ -24,7 +27,7 @@ require (
github.com/pmezard/go-difflib v1.0.0
github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e
github.com/sirupsen/logrus v1.8.1
github.com/spf13/afero v1.1.2
github.com/spf13/afero v1.2.2
github.com/spf13/cobra v1.4.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.0
Expand All @@ -40,6 +43,13 @@ require (
gopkg.in/yaml.v3 v3.0.1
)

require (
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/mstoykov/k6-taskqueue-lib v0.1.0 // indirect
)

require (
github.com/andybalholm/cascadia v1.3.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
Expand All @@ -48,10 +58,8 @@ require (
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mstoykov/envconfig v1.4.1-0.20220114105314-765c6d8c76f1
github.com/onsi/ginkgo v1.14.0 // indirect
github.com/onsi/gomega v1.10.1 // indirect
github.com/tidwall/match v1.1.1 // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/genproto v0.0.0-20200903010400-9bfcb5116336 // indirect
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08 // indirect
)
81 changes: 76 additions & 5 deletions go.sum

Large diffs are not rendered by default.

29 changes: 18 additions & 11 deletions js/initcontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ import (
"go.k6.io/k6/lib"
"go.k6.io/k6/lib/fsext"
"go.k6.io/k6/loader"

"github.com/grafana/xk6-redis/redis"
"github.com/grafana/xk6-timers/timers"
expws "github.com/grafana/xk6-websockets/websockets"
)

type programWithSource struct {
Expand Down Expand Up @@ -370,17 +374,20 @@ func (i *InitContext) allowOnlyOpenedFiles() {

func getInternalJSModules() map[string]interface{} {
return map[string]interface{}{
"k6": k6.New(),
"k6/crypto": crypto.New(),
"k6/crypto/x509": x509.New(),
"k6/data": data.New(),
"k6/encoding": encoding.New(),
"k6/execution": execution.New(),
"k6/net/grpc": grpc.New(),
"k6/html": html.New(),
"k6/http": http.New(),
"k6/metrics": metrics.New(),
"k6/ws": ws.New(),
"k6": k6.New(),
"k6/crypto": crypto.New(),
"k6/crypto/x509": x509.New(),
"k6/data": data.New(),
"k6/encoding": encoding.New(),
"k6/execution": execution.New(),
"k6/experimental/redis": redis.New(),
"k6/experimental/websockets": &expws.RootModule{},
"k6/experimental/timers": timers.New(),
"k6/net/grpc": grpc.New(),
"k6/html": html.New(),
"k6/http": http.New(),
"k6/metrics": metrics.New(),
"k6/ws": ws.New(),
}
}

Expand Down
10 changes: 10 additions & 0 deletions js/modules/k6/experimental/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Experimental Modules

This folder are here as a documentation and reference point for k6's experimental modules.

Although [accessible in k6 scripts](../../../initcontext.go) under the `k6/experimental` import path, those modules implementations live in their own repository and are not part of the k6 stable release yet:
* [`k6/experimental/k6-redis`](https://github.com/grafana/xk6-redis)
* [`k6/experimental/k6-websockets`](https://github.com/grafana/xk6-websockets)
* [`k6/experimental/k6-timers`](https://github.com/grafana/xk6-timers)

While we intend to keep these modules as stable as possible, we may need to add features or introduce breaking changes. This could happen at any time until we release the module as stable. **use them at your own risk**.
3 changes: 1 addition & 2 deletions output/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@ type Params struct {

// TODO: make v2 with buffered channels?

// An Output abstracts the process of funneling samples to an external storage
// backend, such as a file or something like an InfluxDB instance.
// An Output abstracts the pro uch as a file or something like an InfluxDB instance.
//
// N.B: All outputs should have non-blocking AddMetricSamples() methods and
// should spawn their own goroutine to flush metrics asynchronously.
Expand Down
8 changes: 8 additions & 0 deletions samples/experimental/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
version: '3.3'

services:
k6-experimental-redis:
image: 'redis:alpine'
ports:
- '6379:6379'
command: redis-server
87 changes: 87 additions & 0 deletions samples/experimental/redis.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { check } from "k6";
import http from "k6/http";
import redis from "k6/experimental/redis";
import exec from "k6/execution";
import { textSummary } from "https://jslib.k6.io/k6-summary/0.0.1/index.js";
export const options = {
scenarios: {
redisPerformance: {
executor: "shared-iterations",
vus: 10,
iterations: 200,
exec: "measureRedisPerformance",
},
usingRedisData: {
executor: "shared-iterations",
vus: 10,
iterations: 200,
exec: "measureUsingRedisData",
},
},
};
// Get the redis instance(s) address and password from the environment
const redis_addrs = __ENV.REDIS_ADDRS || "";
const redis_password = __ENV.REDIS_PASSWORD || "";
// Instantiate a new redis client
const redisClient = new redis.Client({
addrs: redis_addrs.split(",") || new Array("localhost:6379"), // in the form of 'host:port', separated by commas
password: redis_password,
});
// Prepare an array of crocodile ids for later use
// in the context of the measureUsingRedisData function.
const crocodileIDs = new Array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
export function measureRedisPerformance() {
// VUs are executed in a parallel fashion,
// thus, to ensure that parallel VUs are not
// modifying the same key at the same time,
// we use keys indexed by the VU id.
const key = `foo-${exec.vu.idInTest}`;
redisClient
.set(`foo-${exec.vu.idInTest}`, 1)
.then(() => redisClient.get(`foo-${exec.vu.idInTest}`))
.then((value) => redisClient.incrBy(`foo-${exec.vu.idInTest}`, value))
.then((_) => redisClient.del(`foo-${exec.vu.idInTest}`))
.then((_) => redisClient.exists(`foo-${exec.vu.idInTest}`))
.then((exists) => {
if (exists !== 0) {
throw new Error("foo should have been deleted");
}
});
}
export function setup() {
redisClient.sadd("crocodile_ids", ...crocodileIDs);
}
export function measureUsingRedisData() {
// Pick a random crocodile id from the dedicated redis set,
// we have filled in setup().
redisClient
.srandmember("crocodile_ids")
.then((randomID) => {
const url = `https://test-api.k6.io/public/crocodiles/${randomID}`;
const res = http.get(url);
check(res, {
"status is 200": (r) => r.status === 200,
"content-type is application/json": (r) =>
r.headers["content-type"] === "application/json",
});
return url;
})
.then((url) => redisClient.hincrby("k6_crocodile_fetched", url, 1));
}
export function teardown() {
redisClient.del("crocodile_ids");
}
export function handleSummary(data) {
redisClient
.hgetall("k6_crocodile_fetched")
.then((fetched) =>
Object.assign(data, { k6_crocodile_fetched: fetched })
)
.then((data) =>
redisClient.set(`k6_report_${Date.now()}`, JSON.stringify(data))
)
.then(() => redisClient.del("k6_crocodile_fetched"));
return {
stdout: textSummary(data, { indent: " ", enableColors: true }),
};
}
33 changes: 33 additions & 0 deletions samples/experimental/timers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// based on https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#reasons_for_delays_longer_than_specified
import { setTimeout } from "k6/experimental/timers";
let last = 0;
let iterations = 10;

function timeout() {
// log the time of this call
logline(new Date().getMilliseconds());

// if we are not finished, schedule the next call
if (iterations-- > 0) {
setTimeout(timeout, 0);
}
}

export default function () {
// initialize iteration count and the starting timestamp
iterations = 10;
last = new Date().getMilliseconds();

// start timer
setTimeout(timeout, 0);
}

function pad(number) {
return number.toString().padStart(3, "0");
}

function logline(now) {
// log the last timestamp, the new timestamp, and the difference
console.log(`${pad(last)} ${pad(now)} ${now - last}`);
last = now;
}
78 changes: 78 additions & 0 deletions samples/experimental/ws.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import {
randomString,
randomIntBetween,
} from "https://jslib.k6.io/k6-utils/1.1.0/index.js";
import { WebSocket } from "k6/experimental/websockets";
import {
setTimeout,
clearTimeout,
setInterval,
clearInterval,
} from "k6/experimental/timers";

let chatRoomName = "publicRoom"; // choose your chat room name
let sessionDuration = randomIntBetween(5000, 60000); // user session between 5s and 1m

export default function () {
for (let i = 0; i < 4; i++) {
startWSWorker(i);
}
}

function startWSWorker(id) {
let url = `wss://test-api.k6.io/ws/crocochat/${chatRoomName}/`;
let ws = new WebSocket(url);
ws.addEventListener("open", () => {
ws.send(
JSON.stringify({
event: "SET_NAME",
new_name: `Croc ${__VU}:${id}`,
})
);

ws.addEventListener("message", (e) => {
let msg = JSON.parse(e.data);
if (msg.event === "CHAT_MSG") {
console.log(
`VU ${__VU}:${id} received: ${msg.user} says: ${msg.message}`
);
} else if (msg.event === "ERROR") {
console.error(`VU ${__VU}:${id} received:: ${msg.message}`);
} else {
console.log(
`VU ${__VU}:${id} received unhandled message: ${msg.message}`
);
}
});

let intervalId = setInterval(() => {
ws.send(
JSON.stringify({
event: "SAY",
message: `I'm saying ${randomString(5)}`,
})
);
}, randomIntBetween(2000, 8000)); // say something every 2-8seconds

let timeout1id = setTimeout(function () {
clearInterval(intervalId);
console.log(
`VU ${__VU}:${id}: ${sessionDuration}ms passed, leaving the chat`
);
ws.send(JSON.stringify({ event: "LEAVE" }));
}, sessionDuration);

let timeout2id = setTimeout(function () {
console.log(
`Closing the socket forcefully 3s after graceful LEAVE`
);
ws.close();
}, sessionDuration + 3000);

ws.addEventListener("close", () => {
clearTimeout(timeout1id);
clearTimeout(timeout2id);
console.log(`VU ${__VU}:${id}: disconnected`);
});
});
}
22 changes: 22 additions & 0 deletions vendor/github.com/cespare/xxhash/v2/LICENSE.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 0b30c35

Please sign in to comment.