Documentation
¶
Overview ¶
Package loadrun executes a flow as a load scenario: N virtual users each running the flow in a loop, with per-request latency and outcome aggregated into a merged report.
It is the wiring layer between three pieces that know nothing about each other - the VU scheduler (scenariorunner), the flow engine (flowlocalrunner) and the metrics envelope (loadmetrics). It deliberately contains no YAML parsing (that lives in yamlflowsimplev2) and no presentation (that lives in the reporter).
What a load run costs ¶
A load run reads the database exactly once, at setup: the flow's nodes, edges and variables, and then one node graph per VU. The iteration loop itself holds no database or service handle at all - see vuWorker's fields - and the per-iteration response persistence side-channel is drained and discarded rather than written. (Sub-flow nodes are the exception: they resolve their target through the services they captured at build time, so a flow containing them does read the database per iteration.)
Rebuilding the node graph every iteration was measured and rejected: node implementations hold configuration only, all per-execution mutable state lives in node.FlowNodeRequest (built fresh by each Run) and in the variable map (deep-copied per iteration, ~32ns), so a rebuild buys no isolation. It costs ~52% of a zero-latency iteration for a three-node flow, and more as flows grow, since its cost scales with node count.
Memory flatness is request-node-scoped ¶
Lean mode - which is always on for load runs - drops decoded response bodies from request nodes once assertions have run. It does not propagate into sub-flows (that needs an ExecuteSubFlow signature change), and GraphQL and WebSocket nodes do not implement it. Flows containing those still run under load; their memory does not stay flat, and their requests are not counted in the report.
Index ¶
Constants ¶
const DefaultFrameInterval = 5 * time.Second
DefaultFrameInterval is how often a load run flushes its metrics into an interval frame: the cadence frames are streamed at and abort rules are evaluated at.
Variables ¶
var ( // ErrThresholdsFailed means the run completed but at least one // threshold did not hold. ErrThresholdsFailed = errors.New("load run: thresholds failed") // ErrAborted means the run was stopped early, by an abort rule or by a // Stopper (for example the frame reporter's dead-man switch). ErrAborted = errors.New("load run: aborted") )
Errors a run that executed can end with. Both arrive alongside a complete Result, so the report is still written.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
// ScenarioName is the `load:` block entry this profile came from, or ""
// when the profile was assembled from --vus/--duration/--iterations.
ScenarioName string
// Flow is the already-imported flow to drive.
Flow *mflow.Flow
// Executor is the scheduling strategy. "" means constant-vus.
Executor mload.Executor
// VUs is the number of concurrent virtual users (constant-vus).
VUs int
// Duration bounds the window during which new iterations start
// (constant-vus and constant-arrival-rate).
Duration time.Duration
// MaxIterations bounds the total iterations issued (constant-vus).
MaxIterations int64
// StartVUs, Stages and GracefulRampDown drive ramping-vus; Stages also
// drives ramping-arrival-rate.
StartVUs int
Stages []mload.Stage
GracefulRampDown time.Duration
// Rate, StartRate, TimeUnit, PreAllocatedVUs and MaxVUs drive the
// arrival-rate executors.
Rate float64
StartRate float64
TimeUnit time.Duration
PreAllocatedVUs int
MaxVUs int
// GracefulStop bounds in-flight iterations once a ramping or
// arrival-rate schedule ends.
GracefulStop time.Duration
// ThinkTime is the pause each VU takes after an iteration.
ThinkTime mload.ThinkTime
// Thresholds are evaluated once, against the final report.
Thresholds []mload.Condition
// Abort rules are evaluated every FrameInterval.
Abort []mload.AbortRule
// FrameInterval is how often metrics are flushed into an interval
// frame. Zero means DefaultFrameInterval.
FrameInterval time.Duration
// OnFrame, when set, receives every interval frame, including the final
// partial one. It is called from the run's metrics goroutine and must
// not block.
OnFrame func(IntervalFrame)
// Stopper, when set, lets a caller end the run early; Run creates its
// own otherwise. Either way abort rules stop the run through it.
Stopper *Stopper
}
Config is a resolved load profile: what to run, how it is scheduled, what it must achieve, and the hooks that observe it while it runs.
func ConfigFromScenario ¶
ConfigFromScenario adapts a `load:` block scenario to a runnable Config. The flow must be the one the scenario names; resolving the name is the caller's job, since only it knows the imported workspace.
func ResolveConfig ¶
func ResolveConfig(opts Options, scenarios []mload.Scenario, flows []mflow.Flow, flowNameArg string) (Config, error)
ResolveConfig turns the command line plus the workflow file into a runnable profile.
flowNameArg is the optional positional flow argument. It is only consulted for flag-driven runs: a scenario already names its flow, and --scenario is mutually exclusive with the profile flags.
The returned Config points into flows, so callers keep the identity of the flow they passed in.
type IntervalFrame ¶
type IntervalFrame struct {
// Seq numbers frames from zero, contiguously.
Seq int64
Frame loadmetrics.Frame
// ActiveVUs is the number of VUs mid-iteration when the frame was cut.
ActiveVUs int64
// Dropped is the cumulative count of dropped arrival-rate iterations.
Dropped int64
// Final marks the last frame of the run.
Final bool
}
IntervalFrame is one interval's metrics, combined across every VU. Its Frame keeps a histogram per (step, status class), so frames from several machines can be combined losslessly.
type Options ¶
type Options struct {
// Requested is set by the command layer when the user passed any load
// flag at all - including one whose value happens to be a zero, like
// `--vus 0` or `--duration 30s` with no --vus. Those are load runs the
// user got wrong, and they deserve a load-mode error rather than a
// silent fall-through to a functional run.
Requested bool
// Scenario names an entry of the file's `load:` block.
Scenario string
// VUs, Duration and Iterations describe a profile inline, for runs that
// do not want a scenario in the file.
VUs int
Duration time.Duration
Iterations int64
// VUsScale and RateScale size the profile for one machine of a run
// split across several: VU counts are multiplied by VUsScale and
// arrival rates by RateScale (see mload.Scenario.Scaled). Zero means 1.
VUsScale float64
RateScale float64
}
Options is the load-mode command line, before it has been reconciled with the workflow file.
func (Options) Enabled ¶
Enabled reports whether the user asked for a load run at all. Everything else about the invocation behaves exactly as it did before load mode existed when this is false.
Values are honoured as well as Requested, so Options assembled without a command line - in a test, or by a future caller - still work.
type Result ¶
type Result struct {
// Config is the profile that was executed.
Config Config
// Summary is the scheduler's view: iterations completed, iterations that
// returned an error, interrupted and dropped iterations, wall time.
Summary scenariorunner.Summary
// Report is the merged metrics report keyed by (step, status class).
Report loadmetrics.Report
// ByStep is the same data folded across status classes, so each step has
// exactly one row. This is what the console table renders.
ByStep loadmetrics.Report
// Thresholds holds one verdict per configured threshold, in order.
Thresholds []ThresholdResult
// AbortReason says why the run stopped early, or is "" when it ran its
// course.
AbortReason string
// Frames is how many interval frames the run produced.
Frames int64
}
Result is everything a completed load run produced.
func Run ¶
func Run(ctx context.Context, cfg Config, services runner.RunnerServices, logger *slog.Logger) (Result, error)
Run executes cfg and returns the merged report.
A completed run is a success even when individual requests failed: request errors are data, reported in Summary.Errors and in the report's error counts. Run returns an error when the run could not meaningfully happen - invalid configuration, a failure setting up the flow graph, or every virtual user failing its very first iteration (which means the target was never reachable, not that the system under test is slow). A run that executed can also end with ErrAborted (an abort rule or the Stopper ended it early) and/or ErrThresholdsFailed, alongside its complete Result.
func (Result) Ran ¶
Ran reports whether the scenario got as far as executing, and therefore whether this Result is worth reporting.
It is true even for runs that ended in an error, because those are exactly the runs whose numbers matter most: a soak that failed its first iteration per VU and then ran cleanly for half an hour still exits non-zero, but throwing its report away would be the worst possible response to it. It is false only when Run failed before any iteration could start - invalid configuration, or a flow graph that would not build.
func (Result) ThresholdsPassed ¶
ThresholdsPassed reports whether every threshold held. It is true when none were configured.
type Stopper ¶
type Stopper struct {
// contains filtered or unexported fields
}
Stopper ends a load run early. The first Stop wins: its reason is the one the run reports. A Stopper is safe for concurrent use and for use before the run starts.
func NewStopper ¶
func NewStopper() *Stopper
NewStopper returns a Stopper that has not been stopped.
func (*Stopper) Done ¶
func (s *Stopper) Done() <-chan struct{}
Done is closed once Stop has been called.
type ThresholdResult ¶
type ThresholdResult struct {
Condition mload.Condition
// Observed is the measured value, in the condition's Limit units. It is
// meaningless when HasData is false.
Observed float64
// HasData is false when the run recorded no request the threshold could
// be measured on, e.g. a step that never ran. Such a threshold fails:
// a gate that cannot be checked must not pass silently.
HasData bool
Passed bool
}
ThresholdResult is the verdict of one threshold against a completed run.
func EvaluateThresholds ¶
func EvaluateThresholds(conditions []mload.Condition, byStep loadmetrics.Report) []ThresholdResult
EvaluateThresholds checks every condition against a by-step report (one row per step, status classes folded), in order.
func (ThresholdResult) ObservedText ¶
func (r ThresholdResult) ObservedText() string
ObservedText renders the observed value for a report.