pulse

package module
v0.3.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 26, 2026 License: MIT Imports: 13 Imported by: 0

README

Pulse

Pulse is a programmable reliability and load testing engine written in Go.

Lightweight, deterministic, and designed for real-world automation.

It generates controlled HTTP load against a target, collects latency and error metrics, and evaluates configurable pass/fail thresholds. Tests are driven by a YAML config file and executed through the pulse CLI.

Quick Start

From the repository root, with a Go toolchain matching go.mod:

1. Start the mock HTTP server (listens on :8080 by default):

go run ./cmd/mockserver -mode healthy

2. In another terminal, run a load test against the examples (they target http://localhost:8080):

go run ./cmd/pulse run examples/baseline.yaml

3. Print results as JSON on stdout:

go run ./cmd/pulse run examples/baseline.yaml --json

After installing the binaries:

go install ./cmd/pulse
go install ./cmd/mockserver

You can run pulse and mockserver from your PATH instead of using go run.

Use as a library in your Go project:

go get algoryn.io/pulse@latest

Expected results (with the mock server in the suggested mode from Examples):


Features

  • Arrival-rate scheduling — request-driven load (requests/sec), with constant, ramp, step, and spike phases (not user/VU-based)
  • Bounded concurrency — configurable goroutine limit prevents runaway resource usage
  • Metrics aggregation — total, failed, RPS, latency (min, mean, p50, p95, p99, max), status code distribution, normalized error categories
  • Thresholdserror_rate, mean_latency, p95_latency, p99_latency with PASS / FAIL in the text report
  • HTTP transport — GET, POST, PUT, DELETE, PATCH; optional headers, body, and timeout in YAML
  • CLIpulse run <config.yaml> with human-readable text and JSON output modes
  • Result hook — optional OnResult callback in Config for post-run integrations (CI systems, observability pipelines)
  • Middleware pipeline — composable Middleware type with Chain and Apply helpers
  • Chaos engineeringWithLatency and WithErrorRate for fault injection
  • go test integrationRunT and SkipIfShort for load testing inside go test

Mock Server

Pulse includes a built-in mock HTTP server for local testing and demos (cmd/mockserver). It avoids external dependencies while you try the example YAML files.

Run (default address :8080):

go run ./cmd/mockserver -mode healthy

Optional: -addr :9090 to listen on another port (then set target.url in your YAML accordingly).

Mode Behavior
healthy Always responds 200 OK quickly with a short body.
mixed-errors Alternates 200 and 500 on successive requests (deterministic).
slow Sleeps 120ms before each 200 — useful with examples/timeout.yaml (short client timeout).
go run ./cmd/mockserver -mode mixed-errors
go run ./cmd/mockserver -mode slow

Usage

1. Write a config file
phases:
  - type: constant
    duration: 30s
    arrivalRate: 50

  - type: ramp
    duration: 30s
    from: 10
    to: 100

  - type: step
    duration: 60s
    from: 10
    to: 100
    steps: 5

  - type: spike
    duration: 60s
    from: 20
    to: 300
    spikeAt: 20s
    spikeDuration: 10s

target:
  method: GET
  url: https://api.example.com/health

maxConcurrency: 100

thresholds:
  errorRate: 0.01       # fail if error rate exceeds 1%
  maxMeanLatency: 200ms # fail if mean latency exceeds 200ms
2. Run the test
pulse run config.yaml

Optional flags:

Flag Description
--json Print results as JSON to stdout
--out <file> Write results as JSON to a file (can combine with --json to mirror the same JSON to stdout)

Examples

Ready-to-run scenarios live under examples/. By default they use http://localhost:8080 — pair them with go run ./cmd/mockserver in the matching mode (see above). Expected outcomes depend on server behavior.

File Intent Suggested mock mode Example command
baseline.yaml Latency SLOs; all thresholds should PASS on a fast service healthy go run ./cmd/pulse run examples/baseline.yaml
mixed-errors.yaml Strict errorRate; should FAIL when failures exceed the limit mixed-errors go run ./cmd/pulse run examples/mixed-errors.yaml
timeout.yaml Short client timeout vs slow responses; error rate should FAIL slow go run ./cmd/pulse run examples/timeout.yaml
post-json.yaml POST with JSON body and headers healthy (POST body accepted) go run ./cmd/pulse run examples/post-json.yaml
put-json.yaml PUT with JSON body healthy go run ./cmd/pulse run examples/put-json.yaml
step.yaml Step phase: discrete rate levels from 10 to 100 RPS in 5 steps healthy go run ./cmd/pulse run examples/step.yaml
spike.yaml Spike phase: base 20 RPS, burst to 300 RPS for 10s healthy go run ./cmd/pulse run examples/spike.yaml

Exit Codes

The pulse CLI uses exit codes for automation (e.g. CI):

Code Meaning
0 Run finished and all configured thresholds passed (pulse.Run returned no error).
2 Run finished but at least one threshold failed — the error chain contains only *pulse.ThresholdViolationError values.
1 Anything else: invalid usage, config/load failure, I/O error, scheduler/engine failure, or a mix of threshold and non-threshold errors.

JSON Output

With --json, the CLI prints one indented JSON object to stdout. With --out <path>, it writes the same object to a file. Without --json, stdout still shows the text report when a result is available; with --json, stdout is JSON only, and you can still add --out to persist a copy.

Structure:

{
  "summary": {
    "total": 0,
    "failed": 0,
    "rps": 0,
    "duration_ms": 0
  },
  "latency": {
    "min_ms": 0,
    "p50_ms": 0,
    "mean_ms": 0,
    "p95_ms": 0,
    "p99_ms": 0,
    "max_ms": 0
  },
  "status_codes": { "200": 0 },
  "errors": { "http_status_error": 0 },
  "thresholds": [
    { "description": "string", "pass": true }
  ],
  "passed": true
}
  • Durationssummary.duration_ms is the run length in milliseconds (integer). latency.*_ms values are also in milliseconds (floating-point).
  • passedtrue when every configured threshold evaluation succeeded; false if any failed. Aligns with exit code 0 vs 2 for threshold-only failures.
  • thresholds — ordered list of individual checks; each entry has a human-readable description and pass.

{} and [] are valid when that part of the result is empty—for instance, no recorded status codes, no classified errors, or no threshold outcomes to list.


Example output

Text (default):

Total requests: 2250
Failed requests: 12
Duration: 1m0.41s
RPS: 37.25

Min latency: 18ms
P50 latency: 45ms
Mean latency: 52ms
P95 latency: 134ms
P99 latency: 198ms
Max latency: 312ms

Status codes:
  200: 2238
  503: 12

Errors:
  http_status_error: 12

Thresholds:
  PASS error_rate < 0.01
  PASS mean_latency < 200ms

JSON (--json):

{
  "summary": {
    "total": 2250,
    "failed": 12,
    "rps": 37.25,
    "duration_ms": 60410
  },
  "latency": {
    "min_ms": 18,
    "p50_ms": 45,
    "mean_ms": 52,
    "p95_ms": 134,
    "p99_ms": 198,
    "max_ms": 312
  },
  "status_codes": { "200": 2238, "503": 12 },
  "errors": { "http_status_error": 12 },
  "thresholds": [
    { "description": "error_rate < 0.01", "pass": true },
    { "description": "mean_latency < 200ms", "pass": true }
  ],
  "passed": true
}

Architecture

pulse run config.yaml
        │
        ▼
   config.Load()          Parses YAML → pulse.Test
        │
        ▼
    pulse.Run()           Validates inputs, evaluates thresholds
        │
        ▼
    engine.Run()          Orchestrates phases and concurrency
        │
        ▼
  scheduler.Run()         Token-bucket pacing; constant, ramp, step, and spike phases
        │
        ▼
   Scenario func          Executes the HTTP request via transport.HTTPClient
        │
        ▼
  metrics.Aggregator      Records latency, status code, and error per call
        │
        ▼
    pulse.Result          Returned to the CLI for text or JSON rendering
Components
Package Responsibility
pulse (root) Public API — Test, Config, Phase, Run, Result, ResultHook
engine Runs phases in sequence; manages goroutine lifecycle and concurrency limiter
scheduler Fires scenario calls at the configured arrival rate (token bucket)
metrics Thread-safe aggregation of latency, status codes, and normalized error categories
transport Minimal HTTP client (GET, POST, PUT, DELETE, PATCH) built on net/http
config YAML loader — maps file config to pulse.Test
internal Concurrency limiter (semaphore); token bucket helper

Roadmap

v0.2.0 ✓
  • Step and spike phases — discrete and burst arrival-rate scheduling
  • Full HTTP method support — PUT, DELETE, PATCH
  • Result hookOnResult callback for post-run integrations
v0.3.x ✓
  • Algoryn ecosystem — module path migrated to algoryn.io/pulse
  • Fabric integrationToRunEvent connects Pulse to Algoryn ecosystem
  • go test integrationRunT and SkipIfShort
  • Middleware pipelineChain, Apply, WithLatency, WithErrorRate
Upcoming
  • Export formats — CSV, OpenTelemetry
  • gRPC transport
  • More chaos primitivesWithTimeout, WithRetry, WithJitter

Part of Algoryn Fabric

Pulse is part of the Algoryn Fabric ecosystem — an open source infrastructure toolkit for Go teams building reliable products.

Tool What it does Status
Pulse Load testing & chaos engineering v0.3.0
Relay API Gateway & observability coming soon
Beacon Alerting & on-call planned

License

MIT

Documentation

Index

Constants

View Source
const (
	// PhaseTypeConstant represents a constant arrival-rate phase.
	PhaseTypeConstant = model.PhaseTypeConstant
	// PhaseTypeRamp represents a linear ramp between two arrival rates.
	PhaseTypeRamp = model.PhaseTypeRamp
	// PhaseTypeStep represents discrete steps between two arrival rates.
	PhaseTypeStep = model.PhaseTypeStep
	// PhaseTypeSpike represents a temporary spike from a base rate to a peak rate.
	PhaseTypeSpike = model.PhaseTypeSpike
)

Variables

View Source
var (
	ErrInjected = errors.New("pulse: injected fault")
	// ErrBulkheadFull is returned by WithBulkhead when the concurrency
	// limit is reached and the context expires before a slot opens.
	ErrBulkheadFull = errors.New("pulse: bulkhead full")
)

ErrInjected is returned by WithErrorRate when a fault is injected.

View Source
var ErrCircuitOpen = errors.New("pulse: circuit open")

ErrCircuitOpen is returned when the circuit breaker is open and requests are being rejected to simulate cascading failures.

Functions

func Chain added in v0.3.3

func Chain(middlewares ...Middleware) func(Scenario) Scenario

Chain applies middlewares to a Scenario in order. The first middleware is the outermost wrapper.

func SkipIfShort added in v0.3.2

func SkipIfShort(t TB)

SkipIfShort skips the test if -short flag is set.

func ToRunEvent added in v0.3.1

func ToRunEvent(result Result, passed bool, startedAt time.Time) fabricmetrics.RunEvent

ToRunEvent converts a Pulse Result into a fabric.RunEvent, making it compatible with other Algoryn ecosystem tools. The startedAt parameter should be the time the run began. If zero, time.Now() is used as a best-effort approximation.

Types

type Config

type Config struct {
	Phases         []Phase
	MaxConcurrency int
	Thresholds     Thresholds
	OnResult       ResultHook // optional; nil means no-op
}

Config holds execution configuration for a test.

type LatencyStats

type LatencyStats struct {
	Min  time.Duration
	Mean time.Duration
	P50  time.Duration
	P95  time.Duration
	P99  time.Duration
	Max  time.Duration
}

LatencyStats contains aggregate latency data.

type Middleware added in v0.3.3

type Middleware func(Scenario) Scenario

Middleware wraps a Scenario to add behavior before or after execution.

func WithBulkhead added in v0.3.5

func WithBulkhead(maxConcurrent int) Middleware

WithBulkhead returns a Middleware that limits the number of concurrent executions of a scenario.

func WithCircuitBreaker added in v0.3.6

func WithCircuitBreaker(threshold float64, window, timeout time.Duration) Middleware

WithCircuitBreaker returns a Middleware that simulates cascading failures by opening a circuit when the error rate within a time window exceeds the threshold.

func WithErrorRate added in v0.3.3

func WithErrorRate(rate float64) Middleware

WithErrorRate returns a Middleware that causes a percentage of requests to fail without calling the underlying Scenario.

func WithJitter added in v0.3.4

func WithJitter(min, max time.Duration, rate float64) Middleware

WithJitter returns a Middleware that adds random latency between min and max to a percentage of requests.

func WithLatency added in v0.3.3

func WithLatency(d time.Duration, rate float64) Middleware

WithLatency returns a Middleware that adds artificial latency to a percentage of requests.

func WithRetry added in v0.3.5

func WithRetry(n int, backoff time.Duration) Middleware

WithRetry returns a Middleware that retries a failed scenario up to n times with a fixed backoff between attempts.

func WithStatusCode added in v0.3.4

func WithStatusCode(code int, rate float64) Middleware

WithStatusCode returns a Middleware that forces a specific HTTP status code to be returned for a percentage of requests, without calling the underlying Scenario.

func WithTimeout added in v0.3.4

func WithTimeout(d time.Duration) Middleware

WithTimeout returns a Middleware that enforces a maximum duration for each scenario execution.

type Phase

type Phase struct {
	Type        PhaseType
	Duration    time.Duration
	ArrivalRate int
	// From and To are the arrival rates (per second) at the start and end of a ramp or step phase.
	From int
	To   int
	// Steps is the number of discrete rate levels for PhaseTypeStep.
	Steps int
	// SpikeAt is when the spike starts; 0 means immediately.
	SpikeAt time.Duration
	// SpikeDuration is how long the spike lasts.
	SpikeDuration time.Duration
}

Phase defines the minimal execution shape for the MVP.

func (Phase) IsConstant

func (p Phase) IsConstant() bool

IsConstant reports whether p is a constant arrival-rate phase.

func (Phase) IsRamp

func (p Phase) IsRamp() bool

IsRamp reports whether p is a linear ramp phase.

func (Phase) IsSpike

func (p Phase) IsSpike() bool

IsSpike reports whether p is a spike phase.

func (Phase) IsStep

func (p Phase) IsStep() bool

IsStep reports whether p is a stepped ramp phase.

type PhaseType

type PhaseType = model.PhaseType

PhaseType describes how a phase should be executed.

type Result

type Result struct {
	Total             int64
	Failed            int64
	Duration          time.Duration
	RPS               float64
	Latency           LatencyStats
	StatusCounts      map[int]int64
	ErrorCounts       map[string]int64
	ThresholdOutcomes []ThresholdOutcome `json:"-"`
}

Result contains the aggregated outcome of a test run.

func Run

func Run(test Test) (Result, error)

Run validates the test definition and executes it through the engine.

func RunT added in v0.3.2

func RunT(t TB, test Test) Result

RunT runs a Pulse load test as a Go test. It calls t.Fatal if any threshold fails or if the engine returns an error. Metrics are reported via t.Log, visible with go test -v. It returns the Result for additional assertions.

type ResultHook

type ResultHook func(result Result, passed bool)

ResultHook is an optional callback invoked after a test run completes. result contains the full aggregated metrics. passed is true when all configured thresholds were met.

type Scenario

type Scenario func(ctx context.Context) (statusCode int, err error)

Scenario is the user-defined workload executed by Pulse. The int is an HTTP or application status code; use 0 when not applicable.

func Apply added in v0.3.3

func Apply(scenario Scenario, middlewares ...Middleware) Scenario

Apply wraps a Scenario with the given middlewares.

type TB added in v0.3.2

type TB interface {
	Helper()
	Fatalf(format string, args ...any)
	Logf(format string, args ...any)
	Skip(args ...any)
}

TB is the minimal testing interface required by Pulse helpers.

type Test

type Test struct {
	Config   Config
	Scenario Scenario
}

Test is the root public input for a Pulse run.

type ThresholdOutcome

type ThresholdOutcome struct {
	Pass        bool
	Description string
}

ThresholdOutcome records whether a configured threshold passed for a run.

type ThresholdViolationError

type ThresholdViolationError struct {
	Description string
	Actual      any
	Limit       any
}

ThresholdViolationError is returned when a configured threshold is exceeded. Description matches the corresponding ThresholdOutcome description (e.g. "mean_latency < 200ms").

func (*ThresholdViolationError) Error

func (e *ThresholdViolationError) Error() string

type Thresholds

type Thresholds struct {
	ErrorRate      float64
	MaxMeanLatency time.Duration
	MaxP95Latency  time.Duration
	MaxP99Latency  time.Duration
}

Thresholds define basic pass/fail conditions for a run.

Directories

Path Synopsis
cmd
mockserver command
pulse command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL