{"id":14371271,"url":"https://github.com/phymbert/xk6-sse","last_synced_at":"2026-01-02T13:57:11.047Z","repository":{"id":231605979,"uuid":"782133133","full_name":"phymbert/xk6-sse","owner":"phymbert","description":"A k6 extension for Server-Sent Events (SSE)","archived":false,"fork":false,"pushed_at":"2025-08-05T09:42:06.000Z","size":169,"stargazers_count":26,"open_issues_count":3,"forks_count":15,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-08-05T10:10:56.637Z","etag":null,"topics":["k6","sse","sse-client","xk6","xk6-extension"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/phymbert.png","metadata":{"files":{"readme":"README.md","changelog":null,"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,"zenodo":null}},"created_at":"2024-04-04T17:44:27.000Z","updated_at":"2025-08-05T09:42:09.000Z","dependencies_parsed_at":"2024-04-04T21:43:32.083Z","dependency_job_id":"de2a949e-bd0a-40dc-985e-8166977c67a3","html_url":"https://github.com/phymbert/xk6-sse","commit_stats":null,"previous_names":["phymbert/xk6-sse"],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/phymbert/xk6-sse","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phymbert%2Fxk6-sse","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phymbert%2Fxk6-sse/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phymbert%2Fxk6-sse/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phymbert%2Fxk6-sse/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/phymbert","download_url":"https://codeload.github.com/phymbert/xk6-sse/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phymbert%2Fxk6-sse/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271652440,"owners_count":24797063,"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","status":"online","status_checked_at":"2025-08-22T02:00:08.480Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["k6","sse","sse-client","xk6","xk6-extension"],"created_at":"2024-08-27T23:00:57.279Z","updated_at":"2026-01-02T13:57:11.041Z","avatar_url":"https://github.com/phymbert.png","language":"Go","funding_links":[],"categories":["Extensions"],"sub_categories":["Community"],"readme":"# xk6-sse\n\nA [k6](https://go.k6.io/k6) extension for [Server-Sent Events (SSE)](https://en.wikipedia.org/wiki/Server-sent_events).\n\nThis is a k6 **community** extension maintained by the open source community. Since [the k6 announcement](https://github.com/grafana/k6/issues/746#issuecomment-3249781235), no custom build with [xk6](https://github.com/grafana/xk6) is required—just `import sse from \"k6/x/sse\"` and k6 will resolve it automatically.\n\nSee the [community extensions list](https://grafana.com/docs/k6/latest/extensions/explore/#community-extensions) for other community-supported packages. The [K6 SSE Extension design](docs/design/021-sse-api.md) describes the API design.\n\n## Examples\n\n```javascript\nimport sse from \"k6/x/sse\"\nimport {check} from \"k6\"\n\nexport default function () {\n    const url = \"https://echo.websocket.org/.sse\"\n    const params = {\n        method: 'GET',\n        headers: {\n            \"Authorization\": \"Bearer XXXX\"\n        },\n        tags: {\"my_k6s_tag\": \"hello sse\"}\n    }\n\n    const response = sse.open(url, params, function (client) {\n        client.on('open', function open() {\n            console.log('connected')\n        })\n\n        client.on('event', function (event) {\n            console.log(`event id=${event.id}, name=${event.name}, data=${event.data}`)\n            if (parseInt(event.id) === 4) {\n                client.close()\n            }\n        })\n\n        client.on('error', function (e) {\n            console.log('An unexpected error occurred: ', e.error())\n        })\n    })\n\n    check(response, {\"status is 200\": (r) =\u003e r \u0026\u0026 r.status === 200})\n}\n```\n\n### OpenAI LLM IT Bench example\n\nYou can benchmark LLM IT performances like TTFT(Time To First Token), PP(Prompt Processing), TG(Token Generation) and Latency of your LLM inference solution using this extension.\n\nBenchmarking streaming chat completions is a way to compute TTFT from client point of view at scale.\n\nFor example, this snippet from [llama.cpp server bench](https://github.com/ggml-org/llama.cpp/tree/master/tools/server) can also be used to benchmark any LLM inference server (like vLLM):\n\n#### Setup\n\u003cdetails\u003e\n\u003csummary\u003ellm.js\u003c/summary\u003e\n\n```javascript\nimport sse from 'k6/x/sse'\nimport {check, sleep} from 'k6'\nimport {SharedArray} from 'k6/data'\nimport {Counter, Rate, Trend} from 'k6/metrics'\nimport exec from 'k6/execution';\n\n// Server chat completions prefix\nconst server_url = __ENV.SERVER_BENCH_URL ? __ENV.SERVER_BENCH_URL : 'http://localhost:8080/v1'\n\n// Number of total prompts in the dataset - default 10m / 10 seconds/request * number of users\nconst n_prompt = __ENV.SERVER_BENCH_N_PROMPTS ? parseInt(__ENV.SERVER_BENCH_N_PROMPTS) : 600 / 10 * 8\n\n// Model name to request\nconst model = __ENV.SERVER_BENCH_MODEL_ALIAS ? __ENV.SERVER_BENCH_MODEL_ALIAS : 'my-model'\n\n// Dataset path (from https://huggingface.co/datasets/anon8231489123/ShareGPT_Vicuna_unfiltered)\n// wget https://huggingface.co/datasets/anon8231489123/ShareGPT_Vicuna_unfiltered/resolve/main/ShareGPT_V3_unfiltered_cleaned_split.json\nconst dataset_path = __ENV.SERVER_BENCH_DATASET ? __ENV.SERVER_BENCH_DATASET : './ShareGPT_V3_unfiltered_cleaned_split.json'\n\n// Max tokens to predict\nconst max_tokens = __ENV.SERVER_BENCH_MAX_TOKENS ? parseInt(__ENV.SERVER_BENCH_MAX_TOKENS) : 512\n\n// Max prompt tokens\nconst n_prompt_tokens = __ENV.SERVER_BENCH_MAX_PROMPT_TOKENS ? parseInt(__ENV.SERVER_BENCH_MAX_PROMPT_TOKENS) : 1024\n\n// Max slot context\nconst n_ctx_slot = __ENV.SERVER_BENCH_MAX_CONTEXT ? parseInt(__ENV.SERVER_BENCH_MAX_CONTEXT) : 2048\n\nexport function setup() {\n    console.info(`Benchmark config: server_url=${server_url} n_prompt=${n_prompt} model=${model} dataset_path=${dataset_path} max_tokens=${max_tokens}`)\n}\n\nconst data = new SharedArray('conversations', function () {\n    const tokenizer = (message) =\u003e message.split(/[\\s,'\".?]/)\n\n    return JSON.parse(open(dataset_path))\n        // Filter out the conversations with less than 2 turns.\n        .filter(data =\u003e data[\"conversations\"].length \u003e= 2)\n        .filter(data =\u003e data[\"conversations\"][0][\"from\"] === \"human\")\n        .map(data =\u003e {\n            return {\n                prompt: data[\"conversations\"][0][\"value\"],\n                n_prompt_tokens: tokenizer(data[\"conversations\"][0][\"value\"]).length,\n                n_completion_tokens: tokenizer(data[\"conversations\"][1][\"value\"]).length,\n            }\n        })\n        // Filter out too short sequences\n        .filter(conv =\u003e conv.n_prompt_tokens \u003e= 4 \u0026\u0026 conv.n_completion_tokens \u003e= 4)\n        // Filter out too long sequences.\n        .filter(conv =\u003e conv.n_prompt_tokens \u003c= n_prompt_tokens \u0026\u0026 conv.n_prompt_tokens + conv.n_completion_tokens \u003c= n_ctx_slot)\n        // Keep only first n prompts\n        .slice(0, n_prompt)\n})\n\nconst llm_prompt_tokens = new Trend('llm_prompt_tokens')\nconst llm_completion_tokens = new Trend('llm_completion_tokens')\n\nconst llm_tokens_second = new Trend('llm_tokens_second')\nconst llm_prompt_processing_second = new Trend('llm_prompt_processing_second')\nconst llm_emit_first_token_second = new Trend('llm_emit_first_token_second')\n\nconst llm_prompt_tokens_total_counter = new Counter('llm_prompt_tokens_total_counter')\nconst llm_completion_tokens_total_counter = new Counter('llm_completion_tokens_total_counter')\n\nconst llm_completions_truncated_rate = new Rate('llm_completions_truncated_rate')\nconst llm_completions_stop_rate = new Rate('llm_completions_stop_rate')\n\nexport const options = {\n    thresholds: {\n        llm_completions_truncated_rate: [\n            // more than 80% of truncated input will abort the test\n            {threshold: 'rate \u003c 0.8', abortOnFail: true, delayAbortEval: '1m'},\n        ],\n    },\n    duration: '10m',\n    vus: 8,\n}\n\n\nexport default function () {\n    const conversation = data[exec.scenario.iterationInInstance % data.length]\n    const payload = {\n        \"messages\": [\n            {\n                \"role\": \"system\",\n                \"content\": \"You are ChatGPT, an AI assistant.\",\n            },\n            {\n                \"role\": \"user\",\n                \"content\": conversation.prompt,\n            }\n        ],\n        \"model\": model,\n        \"stream\": true,\n        \"stream_options\": {\n            \"include_usage\": true,\n        },\n        \"seed\": 42,\n        \"max_tokens\": max_tokens,\n        \"stop\": [\"\u003c|im_end|\u003e\"] // Fix for not instructed models\n    }\n\n    const params = {method: 'POST', body: JSON.stringify(payload)};\n\n    const startTime = new Date()\n    let promptEvalEndTime = null\n    let prompt_tokens = 0\n    let completions_tokens = 0\n    let finish_reason = null\n    const res = sse.open(`${server_url}/chat/completions`, params, function (client) {\n        client.on('event', function (event) {\n            if (promptEvalEndTime == null) {\n                promptEvalEndTime = new Date()\n                llm_emit_first_token_second.add((promptEvalEndTime - startTime) / 1.e3)\n            }\n\n            if (event.data === '[DONE]' || event.dexamplesata === '') {\n                return\n            }\n\n            let chunk = JSON.parse(event.data)\n\n            if (chunk.choices \u0026\u0026 chunk.choices.length \u003e 0) {\n                let choice = chunk.choices[0]\n                if (choice.finish_reason) {\n                    finish_reason = choice.finish_reason\n                }\n            }\n\n            if (chunk.usage) {\n                prompt_tokens = chunk.usage.prompt_tokens\n                llm_prompt_tokens.add(prompt_tokens)\n                llm_prompt_tokens_total_counter.add(prompt_tokens)\n\n                completions_tokens = chunk.usage.completion_tokens\n                llm_completion_tokens.add(completions_tokens)\n                llm_completion_tokens_total_counter.add(completions_tokens)\n            }\n        })\n\n        client.on('error', function (e) {\n            console.log('An unexpected error occurred: ', e.error());\n            throw e;\n        })\n    })\n\n    check(res, {'success completion': (r) =\u003e r.status === 200})\n\n    const endTime = new Date()\n\n    const promptEvalTime = promptEvalEndTime - startTime\n    if (promptEvalTime \u003e 0) {\n        llm_prompt_processing_second.add(prompt_tokens / (promptEvalEndTime - startTime) * 1.e3)\n    }\n\n    const completion_time = endTime - promptEvalEndTime\n    if (completions_tokens \u003e 0 \u0026\u0026 completion_time \u003e 0) {\n        llm_tokens_second.add(completions_tokens / completion_time * 1.e3)\n    }\n    llm_completions_truncated_rate.add(finish_reason === 'length')\n    llm_completions_stop_rate.add(finish_reason === 'stop')\n\n    sleep(0.3)\n}\n\n```\n\u003c/details\u003e\n\n```shell\n# Start an LLM inference server like vLLM or llama.cpp\nllama-server --hf-repo ggml-org/models --hf-file phi-2/ggml-model-q4_0.gguf -ngl 99\n\n# benchmark LLM IT performances using the SSE extension\n./k6 run --vus 5 --duration 30s  examples/llm.js \n```\n\n#### Results\n\n```text\n         /\\      Grafana   /‾‾/  \n    /\\  /  \\     |\\  __   /  /   \n   /  \\/    \\    | |/ /  /   ‾‾\\ \n  /          \\   |   (  |  (‾)  |\n / __________ \\  |_|\\_\\  \\_____/ \n\n     execution: local\n        script: examples/llm.js\n        output: -\n\n     scenarios: (100.00%) 1 scenario, 5 max VUs, 1m0s max duration (incl. graceful stop):\n              * default: 5 looping VUs for 30s (gracefulStop: 30s)\n\nINFO[0015] Benchmark config: server_url=http://localhost:8080/v1 n_prompt=480 model=my-model dataset_path=./ShareGPT_V3_unfiltered_cleaned_split.json max_tokens=512  source=console\n\n\n  █ THRESHOLDS \n\n    llm_completions_truncated_rate\n    ✓ 'rate \u003c 0.8' rate=0.00%\n\n\n  █ TOTAL RESULTS \n\n    checks_total.......................: 15      0.269252/s\n    checks_succeeded...................: 100.00% 15 out of 15\n    checks_failed......................: 0.00%   0 out of 15\n\n    ✓ success completion\n\n    CUSTOM\n    llm_completion_tokens....................................................: avg=196.2      min=8         med=130       max=471        p(90)=445       p(95)=455.6     \n    llm_completion_tokens_total_counter......................................: 2943    52.827302/s\n    llm_completions_stop_rate................................................: 100.00% 15 out of 15\n    llm_completions_truncated_rate...........................................: 0.00%   0 out of 15\n    llm_emit_first_token_second..............................................: avg=10.9062    min=0.091     med=11.395    max=26.437     p(90)=19.3054   p(95)=21.5496   \n    llm_prompt_processing_second.............................................: avg=65.652326  min=2.761282  med=12.535351 max=769.230769 p(90)=40.70527  p(95)=262.785863\n    llm_prompt_tokens........................................................: avg=135.133333 min=58        med=73        max=470        p(90)=335       p(95)=455.3     \n    llm_prompt_tokens_total_counter..........................................: 2027    36.384961/s\n    llm_tokens_second........................................................: avg=58.074933  min=51.765771 med=59.171598 max=65.57377   p(90)=62.921073 p(95)=64.472131 \n    sse_event................................................................: 2985    53.581208/s\n\n    HTTP\n    http_req_duration........................................................: avg=14.46s     min=1.82s     med=13.2s     max=27.76s     p(90)=26.77s    p(95)=27.39s    \n    http_reqs................................................................: 15      0.269252/s\n\n    EXECUTION\n    iteration_duration.......................................................: avg=14.76s     min=2.12s     med=13.5s     max=28.06s     p(90)=27.07s    p(95)=27.7s     \n    iterations...............................................................: 15      0.269252/s\n    vus......................................................................: 1       min=1        max=5\n    vus_max..................................................................: 5       min=5        max=5\n\n    NETWORK\n    data_received............................................................: 743 kB  13 kB/s\n    data_sent................................................................: 10 kB   182 B/s\n\n\n\n\nrunning (0m55.7s), 0/5 VUs, 15 complete and 0 interrupted iterations\ndefault ✓ [======================================] 5 VUs  30s\n\n```\n\n### License\n\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphymbert%2Fxk6-sse","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fphymbert%2Fxk6-sse","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphymbert%2Fxk6-sse/lists"}