Documentation

Getting Started

kFunk CLI is open source and available on GitHub. Install it globally:

# Using Homebrew
brew install kfunk

# Or using npm
npm install -g kfunk

Writing a Test Script

A kFunk test script is a JavaScript file that exports an object with lifecycle hooks. You can use any npm packages — just install them in your project directory.

// my-test.js
export default {
  // Called once per virtual user before the test loop starts
  async setup(ctx) {
    ctx.state.url = "https://api.example.com/health"
  },

  // Called repeatedly for the test duration
  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")
  },

  // Called once per virtual user after the test loop ends
  async teardown(ctx) {
    // clean up resources
  },
}

Lifecycle Hooks

HookRequiredDescription
setup(ctx)NoInitialize state, open connections, prepare data
run(ctx, metrics)YesThe main test loop body — called repeatedly for the duration
teardown(ctx)NoClean up resources (close connections, etc.)

Context Object (ctx)

PropertyTypeDescription
ctx.vuIdnumberVirtual user ID (0-based)
ctx.stateobjectPersistent state bag for the VU — store connections, counters, etc.

Metrics API

The metrics object passed to run() provides:

MethodDescription
metrics.measure(name, fn)Time an async function and record the duration under name
metrics.startTimer()Start a manual timer — returns a timestamp
metrics.record(name, startTime)Record duration since startTime under name
metrics.increment(name, value?)Increment a counter by value (default 1)

Running a Test

kfunk run my-test.js --vus 10 --duration 30

CLI Options

OptionDescriptionDefault
--vus <n>Number of virtual users (concurrent)10
--duration <s>Test duration in seconds30
--stagger <ms>Delay between spawning each VU (ms)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 (ms)0
--config <path>Path to kfunk config fileauto-detected
--jsonOutput raw JSON instead of formatted tablefalse

Example: 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 10

Example: NoLag Broker Test

Test a NoLag real-time broker with publish/subscribe roundtrip metrics.

// 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 30

Plugin 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), and 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 lists plugin packages (or { name, config } objects for inline config). infra selects which plugin to use for infrastructure. 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:

HookWhereDescription
onTestStart(context)CLITest is starting
onVUStart(vuId, totalVUs)CLI + WorkersA VU is about to start
onVUComplete(vuMetrics)CLI + WorkersA VU finished with its metrics
onProgress(message)CLIProgress update
onTestEnd(context, results)CLITest 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. The infra plugin's deploy() automatically includes event plugin npm packages in the worker image.

Infrastructure Plugins

Infrastructure plugins manage distributed workers on any cloud:

MethodDescription
deploy(options)Deploy workers (builds image with event plugin deps), returns service URL
runDistributed(options)Run a distributed test, returns results
getAuthHeaders(serviceUrl)Get auth headers for worker requests
teardown(options)Destroy deployed workers

Available Plugins

PackageTypeDescription
@kfunk-load/plugin-gcpInfrastructureDeploy and run distributed tests on Google Cloud Run
@kfunk-load/plugin-nolagEventStream test events to a NoLag real-time broker

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 */ },
  }
}

Distributed Testing

For large-scale tests, kFunk can distribute your test across multiple workers. With an infrastructure plugin configured, deployment and execution are seamless:

# Deploy workers (infra plugin builds image and deploys)
kfunk deploy

# Run distributed — no --service-url needed
kfunk run my-test.js --workers 10 --vus 20 --duration 60

The infra plugin handles authentication, image building (including event plugin dependencies), and worker orchestration. Results are aggregated on the CLI after all workers complete.

GCP Cloud Run (via plugin)

Install the GCP plugin and configure it:

npm install @kfunk-load/plugin-gcp
// kfunk.config.js
export default {
  plugins: ["@kfunk-load/plugin-gcp"],
  infra: "@kfunk-load/plugin-gcp",
  "@kfunk-load/plugin-gcp": {
    region: "us-central1",
    serviceName: "kfunk-worker",
  },
}

Prerequisites (GCP)

You need the Google Cloud CLI installed and authenticated.

# Install gcloud CLI
brew install google-cloud-sdk

# Authenticate
gcloud auth login

# Set your GCP project
gcloud config set project YOUR_PROJECT_ID

# Enable required APIs
gcloud services enable run.googleapis.com \
  artifactregistry.googleapis.com \
  cloudbuild.googleapis.com

Deploy & Run

# Deploy worker to Cloud Run
kfunk deploy

# Run a distributed test
kfunk run my-test.js --workers 10 --duration 30

Legacy Mode (without plugins)

You can also use --service-url directly without an infra plugin. This uses the built-in orchestrator — workers must be publicly accessible or you must handle auth separately.

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 percentiles (p50, p90, p95, p99), min/max/avg, throughput, and counter totals. Use --json to get machine-readable output for CI/CD pipelines.