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 ¶
This section is empty.
Variables ¶
This section is empty.
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
// VUs is the number of concurrent virtual users. Must be >= 1.
VUs int
// Duration bounds the window during which new iterations start.
Duration time.Duration
// MaxIterations bounds the total iterations issued across all VUs.
MaxIterations int64
}
Config is a resolved load profile: what to run, how many virtual users, and when to stop.
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 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
}
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, 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
}
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 only 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).
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.