@kfunk-load/cli
v1.1.1
Published
Distributed load testing tool
Readme
kFunk CLI
Distributed load testing tool. Stress-test any system with custom scripts and get detailed latency metrics.
Install
npm install -g @kfunk-load/cliQuick Start
Create a test script:
// my-test.js
export default {
async setup(ctx) {
ctx.state.url = "https://api.example.com/health"
},
async run(ctx, metrics) {
await metrics.measure("http_get", async () => {
const res = await fetch(ctx.state.url)
if (!res.ok) throw new Error(`HTTP ${res.status}`)
await res.json()
})
metrics.increment("requests")
},
async teardown(ctx) {
// clean up resources
},
}Run it:
kfunk run my-test.js --vus 10 --duration 30Test Script Format
A test script exports an object with lifecycle hooks:
| Hook | Required | Description |
|------|----------|-------------|
| setup(ctx) | No | Initialize state, open connections, prepare data |
| run(ctx, metrics) | Yes | Main test loop — called repeatedly for the duration |
| teardown(ctx) | No | Clean up resources |
Context (ctx)
| Property | Type | Description |
|----------|------|-------------|
| ctx.vuId | number | Virtual user ID (0-based) |
| ctx.totalVUs | number | Total number of VUs in this run |
| ctx.state | object | Persistent state bag for the VU |
Metrics API
The metrics object passed to run():
| Method | Description |
|--------|-------------|
| metrics.measure(name, fn) | Time an async function and record duration |
| metrics.startTimer() | Start a manual timer (returns timestamp) |
| metrics.record(name, startTime) | Record duration since startTime |
| metrics.increment(name, value?) | Increment a counter (default 1) |
CLI Options
kfunk run <script>
| Option | Description | Default |
|--------|-------------|---------|
| --vus <n> | Number of virtual users (or VUs per worker in distributed mode) | 10 |
| --duration <s> | Test duration in seconds | 30 |
| --stagger <ms> | Delay between spawning each VU | 0 |
| --service-url <url> | Worker URL (enables distributed mode without an infra plugin) | — |
| --workers <n> | Number of workers (distributed only) | 1 |
| --worker-stagger <ms> | Delay between launching each worker | 0 |
| --config <path> | Path to kfunk config file | auto-detected |
| --json | Output raw JSON | false |
kfunk deploy
Deploy workers using an infrastructure plugin, or fall back to the legacy scripts/deploy.sh script.
kfunk deployIf no infra plugin is configured, the deploy command falls back to the legacy GCP Cloud Run deploy script:
| Environment Variable | Description | Default |
|---------------------|-------------|---------|
| KFUNK_REGION | GCP region to deploy to | us-central1 |
| KFUNK_SERVICE_NAME | Cloud Run service name | kfunk-worker |
Examples
HTTP Load Test
// example-test.js
export default {
async setup(ctx) {
ctx.state.url = "https://jsonplaceholder.typicode.com/posts/1"
},
async run(ctx, metrics) {
await metrics.measure("http_get", async () => {
const res = await fetch(ctx.state.url)
if (!res.ok) throw new Error(`HTTP ${res.status}`)
await res.json()
})
metrics.increment("requests")
},
}kfunk run example-test.js --vus 5 --duration 10NoLag Broker Test
// example-nolag.js
import { NoLag } from "@nolag/js-sdk"
const TOKEN = process.env.NOLAG_TOKEN
const TOPIC = process.env.NOLAG_TOPIC || "kfunk/load-test/messages"
export default {
async setup(ctx) {
if (!TOKEN) throw new Error("NOLAG_TOKEN env var is required")
const client = NoLag(TOKEN, { qos: 1, reconnect: false })
await client.connect()
ctx.state.client = client
ctx.state.seq = 0
ctx.state.pending = new Map()
client.subscribe(TOPIC)
client.on(TOPIC, (data) => {
if (data._vuId === ctx.vuId) {
const sent = ctx.state.pending.get(data._seq)
if (sent) {
ctx.state.lastRoundtrip = performance.now() - sent
ctx.state.pending.delete(data._seq)
}
}
})
},
async run(ctx, metrics) {
const seq = ctx.state.seq++
ctx.state.pending.set(seq, performance.now())
await metrics.measure("publish", async () => {
ctx.state.client.emit(TOPIC, {
_vuId: ctx.vuId, _seq: seq, text: "kfunk"
}, { echo: true })
})
metrics.increment("messages_sent")
await new Promise(r => setTimeout(r, 100))
if (ctx.state.lastRoundtrip !== undefined) {
metrics.record("roundtrip", ctx.state.lastRoundtrip)
metrics.increment("messages_received")
ctx.state.lastRoundtrip = undefined
}
},
async teardown(ctx) {
ctx.state.client?.disconnect()
},
}NOLAG_TOKEN=your_token kfunk run example-nolag.js --vus 10 --duration 30Plugin System
kFunk supports plugins to extend its functionality. There are two plugin types:
- Event plugins stream test lifecycle events in real-time (from both the CLI and distributed workers)
- Infrastructure plugins manage distributed workers on any cloud provider
Configuration
Create a kfunk.config.js file in your project root:
// kfunk.config.js
export default {
plugins: [
"@kfunk-load/plugin-gcp",
{ name: "@kfunk-load/plugin-nolag", config: { apiKey: process.env.NOLAG_KEY } },
],
infra: "@kfunk-load/plugin-gcp",
"@kfunk-load/plugin-gcp": {
region: "us-central1",
serviceName: "kfunk-worker",
},
}plugins— list of plugin package names, or{ name, config }objects for inline configinfra— which plugin to use for infrastructure (must be listed inplugins)- Top-level keys matching a plugin name provide plugin-specific configuration
No config file is required — without one, kFunk runs tests locally with no plugins.
Event Plugins
Event plugins observe the test lifecycle. Their hooks fire on the CLI and inside distributed workers:
| Hook | Where | Description |
|------|-------|-------------|
| onTestStart(context) | CLI | Test is starting |
| onVUStart(vuId, totalVUs) | CLI + Workers | A VU is about to start |
| onVUComplete(vuMetrics) | CLI + Workers | A VU finished |
| onProgress(message) | CLI | Progress update |
| onTestEnd(context, results) | CLI | Test complete with aggregated results |
In distributed mode, event plugin configs are serialized and sent to workers. Workers load the event plugins locally and fire onVUStart/onVUComplete hooks in real-time during VU execution. This means the event plugin npm packages must be installed on the worker image — the infra plugin's deploy() handles this automatically.
Infrastructure Plugins
Infrastructure plugins manage distributed workers on any cloud:
| Method | Description |
|--------|-------------|
| deploy(options) | Deploy workers (builds image with event plugin deps), returns { serviceUrl } |
| runDistributed(options) | Run a distributed test, returns RuntimeResult |
| getAuthHeaders(serviceUrl) | Get auth headers for worker requests |
| teardown(options) | Destroy deployed workers |
When an infra plugin is configured, kfunk deploy delegates to it and kfunk run uses it for distributed execution automatically (no --service-url needed).
Writing a Plugin
A plugin package exports a factory function as its default export:
// Event plugin example
export default function(config) {
return {
name: "my-event-plugin",
type: "event",
async onTestStart(context) { /* ... */ },
async onVUComplete(vuMetrics) { /* ... */ },
async onTestEnd(context, results) { /* ... */ },
async destroy() { /* cleanup */ },
}
}// Infrastructure plugin example
export default function(config) {
return {
name: "my-infra-plugin",
type: "infra",
async deploy(options) { /* ... */ return { serviceUrl } },
async runDistributed(options) { /* ... */ return runtimeResult },
async getAuthHeaders(serviceUrl) { /* ... */ return headers },
async destroy() { /* cleanup */ },
}
}Available Plugins
| Package | Type | Description |
|---------|------|-------------|
| @kfunk-load/plugin-gcp | Infrastructure | Deploy and run distributed tests on Google Cloud Run |
| @kfunk-load/plugin-nolag | Event | Stream test events to a NoLag real-time broker |
Distributed Testing with Plugins
With an infra plugin configured, distributed testing is seamless:
# Deploy workers (infra plugin builds the image and deploys)
kfunk deploy
# Run distributed — no --service-url needed, the plugin handles it
kfunk run my-test.js --workers 10 --vus 20 --duration 60Distributed Test with GCP Cloud Run (Legacy)
You can also use --service-url directly without an infra plugin. This uses the legacy orchestrator (no automatic auth — workers must be publicly accessible or you must handle auth separately).
Prerequisites
- Install the Google Cloud CLI
- Authenticate and set your project:
gcloud auth login
gcloud config set project YOUR_PROJECT_ID- Enable the required APIs:
gcloud services enable run.googleapis.com artifactregistry.googleapis.com cloudbuild.googleapis.comDeploy & Run
Deploy the worker to Cloud Run:
kfunk deployRun a distributed test using the service URL printed by deploy:
kfunk run my-test.js \ --service-url https://kfunk-worker-xxxxx.run.app \ --workers 10 \ --duration 30
Output
kFunk outputs a summary table with:
- Latency stats: min, avg, median, p90, p95, p99, max
- Counter totals
- VU count, total iterations, and duration
Use --json for machine-readable output in CI/CD pipelines.
Using Custom Packages
Your test scripts can import any npm package. Install them in your project directory:
npm install @nolag/js-sdk
kfunk run example-nolag.js --vus 10 --duration 30kFunk resolves packages from your project's node_modules.
License
MIT
