Giter VIP home page Giter VIP logo

xk6-cable's Introduction

xk6-cable

A k6 extension for testing Action Cable and AnyCable functionality. Built for k6 using the xk6 system.

Comparing to the official WebSockets support, xk6-cable provides the following features:

  • Built-in Action Cable API support (no need to manually build or parse protocol messages).
  • Synchronous API to initialize connections and subscriptions.
  • AnyCable-specific extensions (e.g., binary encodings)

Read also "Real-time stress: AnyCable, k6, WebSockets, and Yabeda"

We also provide JS helpers to simplify writing benchmarks for Rails applications.

Build

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

  1. Install xk6 framework for extending k6:
go install go.k6.io/xk6/cmd/xk6@latest
  1. Build the binary:
xk6 build --with github.com/anycable/xk6-cable@latest

# you can specify k6 version
xk6 build v0.42.0 --with github.com/anycable/xk6-cable@latest

# or if you want to build from the local source
xk6 build --with github.com/anycable/xk6-cable@latest=/path/to/source

Example

Consider a simple example using the EchoChannel:

// benchmark.js
import { check, sleep } from 'k6';
import cable from "k6/x/cable";

export default function () {
  // Initialize the connection
  const client = cable.connect("ws://localhost:8080/cable");
  // If connection were not sucessful, the return value is null
  // It's a good practice to add a check and configure a threshold (so, you can fail-fast if
  // configuration is incorrect)
  if (
    !check(client, {
      "successful connection": (obj) => obj,
    })
  ) {
    fail("connection failed");
  }

  // At this point, the client has been successfully connected
  // (e.g., welcome message has been received)

  // Send subscription request and wait for the confirmation.
  // Returns null if failed to subscribe (due to rejection or timeout).
  const channel = client.subscribe("EchoChannel");

  // Perform an action
  channel.perform("echo", { foo: 1 });

  // Retrieve a single message from the incoming inbox (FIFO).
  // Returns null if no messages have been received in the specified period of time (see below).
  const res = channel.receive();
  check(res, {
    "received res": (obj) => obj.foo === 1,
  });

  channel.perform("echo", { foobar: 3 });
  channel.perform("echo", { foobaz: 3 });

  // You can also retrieve multiple messages at a time.
  // Returns as many messages (but not more than expected) as have been received during
  // the specified period of time. If none, returns an empty array.
  const reses = channel.receiveN(2);
  check(reses, {
    "received 2 messages": (obj) => obj.length === 2,
  });

  sleep(1);

  // You can also subscribe to a channel asynchrounsly and wait for the confirmation later
  // That allows to send multiple subscribe commands at once in a non-blocking way
  const channelSubscribed = client.subscribeAsync("EchoChannel", { foo: 1 });
  const anotherChannelSubscribed = client.subscribeAsync("EchoChannel", { foo: 2 });

  // Wait for the confirmation
  channelSubscribed.await();
  anotherChannelSubscribed.await();

  // Terminate the WS connection
  client.disconnect()
}

Example run results:

$ ./k6 run benchmark.js


          /\      |‾‾| /‾‾/   /‾‾/
     /\  /  \     |  |/  /   /  /
    /  \/    \    |     (   /   ‾‾\
   /          \   |  |\  \ |  (‾)  |
  / __________ \  |__| \__\ \_____/ .io

  execution: local
     script: benchmark.js
     output: -

  scenarios: (100.00%) 1 scenario, 1 max VUs, 10m30s max duration (incl. graceful stop):
           * default: 1 iterations for each of 1 VUs (maxDuration: 10m0s, gracefulStop: 30s)


running (00m00.0s), 0/1 VUs, 1 complete and 0 interrupted iterations
default ✓ [======================================] 1 VUs  00m00.0s/10m0s  1/1 iters, 1 per VU

     ✓ received res
     ✓ received res2
     ✓ received 3 messages
     ✓ received 2 messages
     ✓ all messages with baz attr

     checks...............: 100.00% ✓ 5 ✗ 0
     data_received........: 995 B   83 kB/s
     data_sent............: 1.2 kB  104 kB/s
     iteration_duration...: avg=11.06ms  min=11.06ms  med=11.06ms  max=11.06ms  p(90)=11.06ms  p(95)=11.06ms
     iterations...........: 1       83.850411/s
     ws_connecting........: avg=904.62µs min=904.62µs med=904.62µs max=904.62µs p(90)=904.62µs p(95)=904.62µs
     ws_msgs_received.....: 9       754.653698/s
     ws_msgs_sent.........: 9       754.653698/s
     ws_sessions..........: 1       83.850411/s

You can pass the following options to the connect method as the second argument:

{
  headers: {}, // HTTP headers to use (e.g., { COOKIE: 'some=cookie;' })
  cookies: "", // HTTP cookies as string (overwrite the value passed in headers if present)
  tags: {}, // k6 tags
  handshakeTimeoutS: 60, // Max allowed time to initialize a connection
  receiveTimeoutMs: 1000, // Max time to wait for an incoming message
  logLevel: "info" // logging level (change to debug to see more information)
  codec: "json", // Codec (encoder) to use. Supported values are: json, msgpack, protobuf.
}

NOTE: msgpack and protobuf codecs are only supported by AnyCable PRO.

More examples could be found in the examples/ folder.

JS helpers for k6

We provide a collection of utils to simplify development of k6 scripts for Rails applications (w/Action Cable or AnyCable):

import {
  cableUrl, // reads the value of the action-cable-url (or cable-url) meta value
  turboStreamSource, // find and returns a stream name and a channel name from the <turbo-cable-stream-source> element
  csrfToken, // reads the value of the csrf-token meta value
  csrfParam, // reads the value of the csrf-param meta value
  readMeta, // reads the value of the meta tag with the given name
} from 'https://anycable.io/xk6-cable/jslib/k6-rails/0.1.0/index.js'

export default function () {
  let res = http.get("http://localhost:3000/home");

  if (
    !check(res, {
      "is status 200": (r) => r.status === 200,
    })
  ) {
    fail("couldn't open page");
  }

  const html = res.html();
  const wsUrl = cableUrl(html);

  if (!wsUrl) {
    fail("couldn't find cable url on the page");
  }

  let client = cable.connect(wsUrl);

  if (
    !check(client, {
      "successful connection": (obj) => obj,
    })
  ) {
    fail("connection failed");
  }

  let { streamName, channelName } = turboStreamSource(html);

  if (!streamName) {
    fail("couldn't find a turbo stream element");
  }

  let channel = client.subscribe(channelName, {
    signed_stream_name: streamName,
  });

  if (
    !check(channel, {
      "successful subscription": (obj) => obj,
    })
  ) {
    fail("failed to subscribe");
  }

  // ...
}

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/anycable/xk6-cable.

License

The gem is available as open source under the terms of the MIT License.

xk6-cable's People

Contributors

askazakov avatar col3name avatar mstoykov avatar palkan avatar skryukov avatar slayerdf avatar sudobooo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

xk6-cable's Issues

Asynchronous `channel.onMessage(callback)` method

Example of desired interface:

import { check } from "k6";
import cable from "k6/x/cable";

export default function () {
  const client = cable.connect("ws://localhost:8080/cable");
  const channel = client.subscribe("BenchmarkChannel");

  channel.onMessage((msg) => {
    check(msg, {
	    "received msg": (obj) => obj.bar === 2,
	  });
  });
  
  channel.perform("echo", { bar: 2 });

  sleep(1);

  // Terminate the WS connection
  client.disconnect()
}

Required to maintain listing in k6 Extensions Registry

We've recently updated the requirements for maintaining an extension within the listing on our site. As such, please address the following items to maintain your listing within the registry:

  • add the xk6 topic to your repository metadata
  • needs to build with a recent version of k6; minimum v0.43

For more information on these and other listing requirements, please take a look at the registry requirements.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.