metrics

package
v1.2.0 Latest Latest
Warning

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

Go to latest
Published: Oct 7, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

Documentation

Overview

Package metrics records latencies and counters during a run and merges them losslessly across intervals and workers.

Index

Constants

View Source
const MaxErrorExamples = 3

MaxErrorExamples is how many examples a step keeps for each error.

View Source
const MaxExampleBody = 512

MaxExampleBody bounds the request and response bodies of an example.

View Source
const MaxSlowest = 5

MaxSlowest is how many of the slowest requests a step keeps per interval and for the whole run.

Variables

View Source
var PhaseNames = [NumPhases]string{"dns", "connect", "tls", "wait", "download", "firstEvent", "fcp", "lcp", "cls", "inp", "load"}

PhaseNames are the report labels for each phase.

Functions

func TraceIDString

func TraceIDString(id [16]byte) string

TraceIDString returns the trace ID as 32 hex digits, or "" when unset.

Types

type Bucket

type Bucket struct {
	Lo, Hi uint64 // inclusive value range in µs
	Count  uint64
}

Bucket is one non-empty bucket, for export and charts.

type Collector

type Collector struct {
	// contains filtered or unexported fields
}

Collector receives samples from many virtual users concurrently and produces one Snapshot per interval. Users are spread over lock-striped shards so recording does not contend on a single mutex.

func NewCollector

func NewCollector(n int) *Collector

NewCollector creates a collector with n shards (at least 1).

func (*Collector) Dropped

func (c *Collector) Dropped(n uint64)

Dropped counts iterations that could not start because no user was free.

func (*Collector) Flush

func (c *Collector) Flush(interval int64, vus int, planned float64) *Snapshot

Flush closes the current interval and returns its snapshot. vus and planned are gauges sampled by the caller at flush time.

func (*Collector) IterationDone

func (c *Collector) IterationDone(vu, journey int, d time.Duration, ok bool)

IterationDone counts a finished iteration.

func (*Collector) IterationStarted

func (c *Collector) IterationStarted(vu, journey int, lag time.Duration)

IterationStarted counts a journey iteration starting, along with how late it was dispatched compared with its schedule.

func (*Collector) PhaseHistograms

func (c *Collector) PhaseHistograms() map[int]*[NumPhases]*Histogram

PhaseHistograms returns run-wide per-phase histograms for every step.

func (*Collector) Record

func (c *Collector) Record(vu int, s *Sample)

Record adds a completed request from virtual user vu.

type ErrorExample

type ErrorExample struct {
	At      time.Time `json:"at"`
	TraceID string    `json:"traceId,omitempty"`
	// Request is "METHOD URL"; RequestBody its first MaxExampleBody bytes.
	Request         string            `json:"request"`
	RequestHeaders  map[string]string `json:"requestHeaders,omitempty"`
	RequestBody     string            `json:"requestBody,omitempty"`
	Status          int               `json:"status,omitempty"`
	ResponseHeaders map[string]string `json:"responseHeaders,omitempty"`
	ResponseBody    string            `json:"responseBody,omitempty"`
	// Detail is the error message, such as a check's mismatch or a
	// connection error.
	Detail string `json:"detail,omitempty"`
	// Browser steps: a JPEG screenshot of the page when it failed, its
	// console errors and warnings, and a HAR of its requests (no bodies).
	Screenshot []byte   `json:"screenshot,omitempty"`
	Console    []string `json:"console,omitempty"`
	HAR        string   `json:"har,omitempty"`
}

ErrorExample shows one failed request: what was sent and what came back, with credentials and the run's secrets redacted.

type Histogram

type Histogram struct {
	// contains filtered or unexported fields
}

Histogram is a log-linear latency histogram with the same bucketing as HdrHistogram at 3 significant digits: values below 2048 µs are exact and larger values are kept within 0.1% relative error. Storage is chunked and allocated on demand, so a histogram covering a narrow latency band costs a few KB instead of HdrHistogram's ~140 KB dense array. That matters because a worker keeps one per step per second.

Values are recorded in microseconds. Histograms are not safe for concurrent use.

func NewHistogram

func NewHistogram() *Histogram

NewHistogram returns an empty histogram.

func (*Histogram) Buckets

func (h *Histogram) Buckets() []Bucket

Buckets lists non-empty buckets in ascending order.

func (*Histogram) Clone

func (h *Histogram) Clone() *Histogram

Clone returns a deep copy.

func (*Histogram) Count

func (h *Histogram) Count() uint64

Count is the number of recorded values.

func (*Histogram) MarshalBinary

func (h *Histogram) MarshalBinary() ([]byte, error)

MarshalBinary encodes the histogram compactly (sparse, varint).

func (*Histogram) MarshalText

func (h *Histogram) MarshalText() ([]byte, error)

MarshalText encodes the histogram as base64 of its binary form, so it can travel inside JSON.

func (*Histogram) Max

func (h *Histogram) Max() uint64

Max is the largest recorded value in µs (0 when empty).

func (*Histogram) Mean

func (h *Histogram) Mean() float64

Mean is the exact arithmetic mean in µs.

func (*Histogram) Merge

func (h *Histogram) Merge(o *Histogram)

Merge adds every value from o into h.

func (*Histogram) Min

func (h *Histogram) Min() uint64

Min is the smallest recorded value in µs (0 when empty).

func (*Histogram) Quantile

func (h *Histogram) Quantile(q float64) uint64

Quantile returns the value at quantile q (0..1) in µs, reported as the highest value equivalent to the bucket, as HdrHistogram does. The result never exceeds the recorded maximum.

func (*Histogram) QuantileSeconds

func (h *Histogram) QuantileSeconds(q float64) float64

QuantileSeconds is Quantile converted to seconds.

func (*Histogram) Quantiles

func (h *Histogram) Quantiles(qs ...float64) []uint64

Quantiles returns several quantiles in one pass. qs must be ascending.

func (*Histogram) Record

func (h *Histogram) Record(us uint64)

Record adds one value in microseconds.

func (*Histogram) RecordDuration

func (h *Histogram) RecordDuration(d time.Duration)

RecordDuration adds one duration, rounded to microseconds.

func (*Histogram) RecordN

func (h *Histogram) RecordN(us uint64, n uint64)

RecordN adds n occurrences of a value in microseconds.

func (*Histogram) Reset

func (h *Histogram) Reset()

Reset empties the histogram, keeping allocated chunks for reuse.

func (*Histogram) Sum

func (h *Histogram) Sum() uint64

Sum is the exact total of recorded values in µs.

func (*Histogram) Summary

func (h *Histogram) Summary() Percentiles

Summary computes the standard percentile set in seconds.

func (*Histogram) UnmarshalBinary

func (h *Histogram) UnmarshalBinary(data []byte) error

UnmarshalBinary decodes data produced by MarshalBinary, replacing h.

func (*Histogram) UnmarshalText

func (h *Histogram) UnmarshalText(text []byte) error

UnmarshalText decodes MarshalText output.

type JourneyStats

type JourneyStats struct {
	Started   uint64     `json:"started"`
	Completed uint64     `json:"completed"`
	Failed    uint64     `json:"failed"`
	Duration  *Histogram `json:"duration"`
}

JourneyStats aggregates whole iterations of a journey.

func (*JourneyStats) Merge

func (j *JourneyStats) Merge(o *JourneyStats)

Merge adds o into j.

type Percentiles

type Percentiles struct {
	Count uint64  `json:"count"`
	Min   float64 `json:"min"`
	Mean  float64 `json:"mean"`
	P50   float64 `json:"p50"`
	P90   float64 `json:"p90"`
	P95   float64 `json:"p95"`
	P99   float64 `json:"p99"`
	P999  float64 `json:"p999"`
	Max   float64 `json:"max"`
}

Percentiles is a fixed summary of a histogram, in seconds.

type Phase

type Phase int

Phase indexes per-request timing phases.

const (
	PhaseDNS Phase = iota
	PhaseConnect
	PhaseTLS
	PhaseWait // time to first byte after the request was written
	PhaseDownload
	// PhaseFirstEvent is the time from the start of a streaming step to
	// its first event: time to first token for an LLM API.
	PhaseFirstEvent
	// Browser page loads: first contentful paint, largest contentful
	// paint, cumulative layout shift, interaction to next paint and the
	// load event, each from the start of the navigation or interaction.
	// CLS is unitless; it is stored as CLS seconds (a shift of 0.1 is
	// recorded as 100ms) so it fits the same histograms.
	PhaseFCP
	PhaseLCP
	PhaseCLS
	PhaseINP
	PhaseLoad
	NumPhases
)

Request phases, as measured by net/http/httptrace, plus the time to the first event of a stream (server-sent events, gRPC server streaming). New phases are only ever appended: workers and servers exchange phases by index, and a peer that knows fewer phases leaves the rest empty.

type Sample

type Sample struct {
	Step int
	// Intended is when the request should have been sent. In open-model
	// runs a late dispatch makes it earlier than Start; latency is
	// measured from here to avoid coordinated omission.
	Intended time.Time
	Start    time.Time
	End      time.Time
	Phases   [NumPhases]time.Duration
	Status   int
	// Err is an error class ("" for success at the transport level).
	Err          string
	Failed       bool
	ChecksPassed int
	ChecksFailed int
	BytesIn      int64
	BytesOut     int64
	// Proto is the negotiated protocol, such as "HTTP/1.1" or "HTTP/2.0".
	Proto string
	// Events counts the events or messages a streaming step received;
	// StreamTime is the time from the first of them to the end of the step.
	Events     int
	StreamTime time.Duration
	// TraceID is the W3C trace ID sent with the request (all zero when
	// none was sent).
	TraceID [16]byte
	// Exchange, set on some failed samples, is an example of the failure
	// for the report (see MaxErrorExamples).
	Exchange *ErrorExample
}

Sample is one completed request.

type SlowRequest

type SlowRequest struct {
	// Latency is measured from the intended send time, like StepStats.Latency.
	Latency time.Duration `json:"latency"`
	// Start is when the request was sent.
	Start time.Time `json:"start"`
	// TraceID is the 32-hex-digit W3C trace ID, empty when none was sent.
	TraceID string `json:"traceId,omitempty"`
	Status  int    `json:"status,omitempty"`
	Err     string `json:"error,omitempty"`
}

SlowRequest is one of the slowest requests of a step.

type Snapshot

type Snapshot struct {
	// Interval is the number of whole intervals since the run's T0.
	Interval int64 `json:"interval"`
	// Seq increases by one per snapshot from a worker so resends are
	// detected and never counted twice.
	Seq      uint64                `json:"seq,omitempty"`
	Worker   string                `json:"worker,omitempty"`
	Steps    map[int]*StepStats    `json:"steps,omitempty"`
	Journeys map[int]*JourneyStats `json:"journeys,omitempty"`
	// Dropped counts open-model iterations that could not start because
	// no virtual user was free.
	Dropped uint64 `json:"dropped,omitempty"`
	// SchedLag is how late iterations were dispatched versus schedule.
	SchedLag *Histogram `json:"schedLag,omitempty"`
	// VUs is the number of active virtual users at the end of the interval.
	VUs int `json:"vus"`
	// Planned is the planned load (users or rate) at the interval.
	Planned float64 `json:"planned,omitempty"`
}

Snapshot holds everything recorded during one interval (normally one second) by one worker, or the merge of many.

func NewSnapshot

func NewSnapshot(interval int64) *Snapshot

NewSnapshot returns an empty snapshot for an interval.

func (*Snapshot) Empty

func (s *Snapshot) Empty() bool

Empty reports whether nothing was recorded.

func (*Snapshot) Journey

func (s *Snapshot) Journey(id int) *JourneyStats

Journey returns the stats for a journey, creating them when absent.

func (*Snapshot) Merge

func (s *Snapshot) Merge(o *Snapshot)

Merge adds every count and histogram in o into s. Gauges (VUs) are summed, which is right when merging different workers for the same interval.

func (*Snapshot) Step

func (s *Snapshot) Step(id int) *StepStats

Step returns the stats for a step, creating them when absent.

func (*Snapshot) Totals

func (s *Snapshot) Totals() *StepStats

Totals sums all steps of the snapshot into one StepStats.

type StepStats

type StepStats struct {
	Requests     uint64            `json:"requests"`
	Failed       uint64            `json:"failed"`
	Errors       map[string]uint64 `json:"errors,omitempty"`
	Status       map[int]uint64    `json:"status,omitempty"`
	ChecksPassed uint64            `json:"checksPassed,omitempty"`
	ChecksFailed uint64            `json:"checksFailed,omitempty"`
	BytesIn      uint64            `json:"bytesIn"`
	BytesOut     uint64            `json:"bytesOut"`
	// Latency is measured from the intended send time.
	Latency *Histogram `json:"latency"`
	// Service is measured from the actual send time.
	Service *Histogram `json:"service"`
	// PhaseSum holds per-phase totals in µs for computing means.
	PhaseSum [NumPhases]uint64 `json:"phaseSum"`
	// Streams counts samples that received at least one stream event,
	// Events the events they received and StreamUs the µs from each
	// stream's first event to its end. Events per second after the first
	// event is (Events - Streams) / StreamUs.
	Streams  uint64 `json:"streams,omitempty"`
	Events   uint64 `json:"events,omitempty"`
	StreamUs uint64 `json:"streamUs,omitempty"`
	// Protocols counts requests by negotiated protocol (HTTP/1.1, HTTP/2.0).
	Protocols map[string]uint64 `json:"protocols,omitempty"`
	// Slowest holds up to MaxSlowest of the slowest requests, slowest
	// first, with their trace IDs.
	Slowest []SlowRequest `json:"slowest,omitempty"`
	// Examples holds up to MaxErrorExamples failed requests per error.
	Examples map[string][]ErrorExample `json:"examples,omitempty"`
}

StepStats aggregates one request step.

func (*StepStats) Add

func (st *StepStats) Add(s *Sample)

Add records a sample.

func (*StepStats) Merge

func (st *StepStats) Merge(o *StepStats)

Merge adds o into st.

Jump to

Keyboard shortcuts

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