Skip to content

Commit

Permalink
Merge pull request #2630 from grafana/feature/js/experimental_redis
Browse files Browse the repository at this point in the history
Introduce experimental JS modules
  • Loading branch information
oleiade authored Aug 16, 2022
2 parents fc74425 + da648a9 commit d191029
Show file tree
Hide file tree
Showing 77 changed files with 21,117 additions and 24 deletions.
14 changes: 11 additions & 3 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.1
github.com/grafana/xk6-websockets v0.1.1
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 Down Expand Up @@ -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
)
82 changes: 77 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 @@ -31,6 +31,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 @@ -350,17 +354,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**.
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.

69 changes: 69 additions & 0 deletions vendor/github.com/cespare/xxhash/v2/README.md

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

Loading

0 comments on commit d191029

Please sign in to comment.