{"id":14371062,"url":"https://github.com/anycable/xk6-cable","last_synced_at":"2025-04-26T09:32:08.032Z","repository":{"id":38350262,"uuid":"370995437","full_name":"anycable/xk6-cable","owner":"anycable","description":"A k6 extension for testing Action Cable and AnyCable functionality","archived":false,"fork":false,"pushed_at":"2024-06-24T17:11:34.000Z","size":173,"stargazers_count":28,"open_issues_count":0,"forks_count":7,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-04T10:36:31.855Z","etag":null,"topics":["anycable","benchmarking","hacktoberfest","k6","k6-extension","xk6"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/anycable.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-05-26T10:33:17.000Z","updated_at":"2025-02-11T21:35:20.000Z","dependencies_parsed_at":"2024-06-24T18:44:35.855Z","dependency_job_id":"3d265763-6562-4404-b872-fcd1e06cca7c","html_url":"https://github.com/anycable/xk6-cable","commit_stats":{"total_commits":50,"total_committers":6,"mean_commits":8.333333333333334,"dds":"0.31999999999999995","last_synced_commit":"4efc679240f1c81e88687b065e5b6ecf447f6ea4"},"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anycable%2Fxk6-cable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anycable%2Fxk6-cable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anycable%2Fxk6-cable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anycable%2Fxk6-cable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/anycable","download_url":"https://codeload.github.com/anycable/xk6-cable/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250967248,"owners_count":21515565,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["anycable","benchmarking","hacktoberfest","k6","k6-extension","xk6"],"created_at":"2024-08-27T23:00:53.196Z","updated_at":"2025-04-26T09:32:07.797Z","avatar_url":"https://github.com/anycable.png","language":"Go","funding_links":[],"categories":["Extensions"],"sub_categories":["Community"],"readme":"# xk6-cable\n\nA k6 extension for testing Action Cable and AnyCable functionality. Built for [k6][] using the [xk6][] system.\n\nComparing to the official [WebSockets support][k6-websockets], `xk6-cable` provides the following features:\n\n- Built-in Action Cable API support (no need to manually build or parse protocol messages).\n- Synchronous API to initialize connections and subscriptions.\n- AnyCable-specific extensions (e.g., binary encodings)\n\n\u003e Read also [\"Real-time stress: AnyCable, k6, WebSockets, and Yabeda\"](https://evilmartians.com/chronicles/real-time-stress-anycable-k6-websockets-and-yabeda?utm_source=xk6-cable-github)\n\nWe also provide [JS helpers](#js-helpers-for-k6) to simplify writing benchmarks for Rails applications.\n\n## Build\n\nTo build a `k6` binary with this extension, first ensure you have the prerequisites:\n\n- [Go toolchain](https://go101.org/article/go-toolchain.html) v1.17+\n- Git\n\n1. Install `xk6` framework for extending `k6`:\n\n```sh\ngo install go.k6.io/xk6/cmd/xk6@latest\n```\n\n2. Build the binary:\n\n```shell\nxk6 build --with github.com/anycable/xk6-cable@latest\n\n# you can specify k6 version\nxk6 build v0.42.0 --with github.com/anycable/xk6-cable@latest\n\n# or if you want to build from the local source\nxk6 build --with github.com/anycable/xk6-cable@latest=/path/to/source\n```\n\n## Example\n\nConsider a simple example using the EchoChannel:\n\n```js\n// benchmark.js\nimport { check, sleep } from 'k6';\nimport cable from \"k6/x/cable\";\n\nexport default function () {\n  // Initialize the connection\n  const client = cable.connect(\"ws://localhost:8080/cable\");\n  // If connection were not sucessful, the return value is null\n  // It's a good practice to add a check and configure a threshold (so, you can fail-fast if\n  // configuration is incorrect)\n  if (\n    !check(client, {\n      \"successful connection\": (obj) =\u003e obj,\n    })\n  ) {\n    fail(\"connection failed\");\n  }\n\n  // At this point, the client has been successfully connected\n  // (e.g., welcome message has been received)\n\n  // Send subscription request and wait for the confirmation.\n  // Returns null if failed to subscribe (due to rejection or timeout).\n  const channel = client.subscribe(\"EchoChannel\");\n\n  // Perform an action\n  channel.perform(\"echo\", { foo: 1 });\n\n  // Retrieve a single message from the incoming inbox (FIFO).\n  // Returns null if no messages have been received in the specified period of time (see below).\n  const res = channel.receive();\n  check(res, {\n    \"received res\": (obj) =\u003e obj.foo === 1,\n  });\n\n  channel.perform(\"echo\", { foobar: 3 });\n  channel.perform(\"echo\", { foobaz: 3 });\n\n  // You can also retrieve multiple messages at a time.\n  // Returns as many messages (but not more than expected) as have been received during\n  // the specified period of time. If none, returns an empty array.\n  const reses = channel.receiveN(2);\n  check(reses, {\n    \"received 2 messages\": (obj) =\u003e obj.length === 2,\n  });\n\n  sleep(1);\n\n  // You can also subscribe to a channel asynchrounsly and wait for the confirmation later\n  // That allows to send multiple subscribe commands at once in a non-blocking way\n  const channelSubscribed = client.subscribeAsync(\"EchoChannel\", { foo: 1 });\n  const anotherChannelSubscribed = client.subscribeAsync(\"EchoChannel\", { foo: 2 });\n\n  // Wait for the confirmation\n  channelSubscribed.await();\n  anotherChannelSubscribed.await();\n\n  // Terminate the WS connection\n  client.disconnect()\n}\n```\n\nExample run results:\n\n```sh\n$ ./k6 run benchmark.js\n\n\n          /\\      |‾‾| /‾‾/   /‾‾/\n     /\\  /  \\     |  |/  /   /  /\n    /  \\/    \\    |     (   /   ‾‾\\\n   /          \\   |  |\\  \\ |  (‾)  |\n  / __________ \\  |__| \\__\\ \\_____/ .io\n\n  execution: local\n     script: benchmark.js\n     output: -\n\n  scenarios: (100.00%) 1 scenario, 1 max VUs, 10m30s max duration (incl. graceful stop):\n           * default: 1 iterations for each of 1 VUs (maxDuration: 10m0s, gracefulStop: 30s)\n\n\nrunning (00m00.0s), 0/1 VUs, 1 complete and 0 interrupted iterations\ndefault ✓ [======================================] 1 VUs  00m00.0s/10m0s  1/1 iters, 1 per VU\n\n     ✓ received res\n     ✓ received res2\n     ✓ received 3 messages\n     ✓ received 2 messages\n     ✓ all messages with baz attr\n\n     checks...............: 100.00% ✓ 5 ✗ 0\n     data_received........: 995 B   83 kB/s\n     data_sent............: 1.2 kB  104 kB/s\n     iteration_duration...: avg=11.06ms  min=11.06ms  med=11.06ms  max=11.06ms  p(90)=11.06ms  p(95)=11.06ms\n     iterations...........: 1       83.850411/s\n     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\n     ws_msgs_received.....: 9       754.653698/s\n     ws_msgs_sent.........: 9       754.653698/s\n     ws_sessions..........: 1       83.850411/s\n```\n\nYou can pass the following options to the `connect` method as the second argument:\n\n```js\n{\n  headers: {}, // HTTP headers to use (e.g., { COOKIE: 'some=cookie;' })\n  cookies: \"\", // HTTP cookies as string (overwrite the value passed in headers if present)\n  tags: {}, // k6 tags\n  handshakeTimeoutS: 60, // Max allowed time to initialize a connection\n  receiveTimeoutMs: 1000, // Max time to wait for an incoming message\n  logLevel: \"info\" // logging level (change to debug to see more information)\n  codec: \"json\", // Codec (encoder) to use. Supported values are: json, msgpack, protobuf.\n}\n```\n\n**NOTE:** `msgpack` and `protobuf` codecs are only supported by [AnyCable PRO](https://anycable.io#pro).\n\nMore examples could be found in the [examples/](./examples) folder.\n\n## JS helpers for k6\n\nWe provide a collection of utils to simplify development of k6 scripts for Rails applications (w/Action Cable or AnyCable):\n\n```js\nimport {\n  cableUrl, // reads the value of the action-cable-url (or cable-url) meta value\n  turboStreamSource, // find and returns a stream name and a channel name from the \u003cturbo-cable-stream-source\u003e element\n  csrfToken, // reads the value of the csrf-token meta value\n  csrfParam, // reads the value of the csrf-param meta value\n  readMeta, // reads the value of the meta tag with the given name\n} from 'https://anycable.io/xk6-cable/jslib/k6-rails/0.1.0/index.js'\n\nexport default function () {\n  let res = http.get(\"http://localhost:3000/home\");\n\n  if (\n    !check(res, {\n      \"is status 200\": (r) =\u003e r.status === 200,\n    })\n  ) {\n    fail(\"couldn't open page\");\n  }\n\n  const html = res.html();\n  const wsUrl = cableUrl(html);\n\n  if (!wsUrl) {\n    fail(\"couldn't find cable url on the page\");\n  }\n\n  let client = cable.connect(wsUrl);\n\n  if (\n    !check(client, {\n      \"successful connection\": (obj) =\u003e obj,\n    })\n  ) {\n    fail(\"connection failed\");\n  }\n\n  let { streamName, channelName } = turboStreamSource(html);\n\n  if (!streamName) {\n    fail(\"couldn't find a turbo stream element\");\n  }\n\n  let channel = client.subscribe(channelName, {\n    signed_stream_name: streamName,\n  });\n\n  if (\n    !check(channel, {\n      \"successful subscription\": (obj) =\u003e obj,\n    })\n  ) {\n    fail(\"failed to subscribe\");\n  }\n\n  // ...\n}\n```\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at [https://github.com/anycable/xk6-cable](https://github.com/anycable/xk6-cable).\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](./LICENSE).\n\n[k6]: https://k6.io\n[xk6]: https://github.com/grafana/xk6\n[k6-websockets]: https://k6.io/docs/using-k6/protocols/websockets/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanycable%2Fxk6-cable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fanycable%2Fxk6-cable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanycable%2Fxk6-cable/lists"}