bench

package
v1.2.9 Latest Latest
Warning

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

Go to latest
Published: Oct 11, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package bench runs one command on a Linux bench against a copy of a local tree: `nova-ci bench run`.

THE HURT. Every card, every read and the coordinator ran Go on a bench with a recipe typed into each brief: ssh <bench> 'mkdir -p <d>' && rsync -a --delete <repo>/ <bench>:<d>/repo/ && ssh <bench> 'cd <d>/repo && export GOCACHE=... GOFLAGS=-mod=readonly NOVA_TEST_NO_HOST=1 && nice -n 19 go ...', a second bench by hand when the first did not answer, then ssh <bench> 'rm -rf <d>'. Each copy of the recipe was a place to get a path, a variable or the cleanup wrong (the owner, 2026-10-05: "We need to get away from these one shot shell scripts."). This package is the recipe, once, behind a Transport the tests fake.

THE RUN, per host, in order:

  1. make: one ssh that makes the root and a fresh run directory under it with mktemp -d, so two runs never share a directory and the run knows exactly the one it made. A host that does not answer (ssh's own exit 255, or ssh not starting) is passed over for the fallback; a host that answers and refuses is the end of the run, never a reason to try another.
  2. copy: the tree into <run>/repo, .git left out unless WithGit; or, with a Stage, the tree staged from the bench's own mirror at one commit (stage_mirror.go), so only the sha crosses the wire. Either way the run's Stage says what it did, and a refusal is a *StageError naming the step, its exit, its stderr's tail and the wall time.
  3. exec: cd <run>/repo, then the command under nice -n 19 with GOCACHE, GOFLAGS=-mod=readonly and NOVA_TEST_NO_HOST=1, its output streamed to the caller as it arrives. Its exit status is the run's.
  4. remove: rm -rf of the run directory step 1 printed and nothing else, whatever happened in 2 and 3, under a context the caller's cancellation does not end, so an interrupted run still cleans up after itself.

THE MODEL. tla/BenchRun.tla is the run as a state machine: TLC holds OnlyTheMadeDirIsRemoved, AtMostOneHostAnswers, FallbackOnlyOnNoAnswer, ExitIsTheCommands and NothingLeftBehind on two hosts (MCBenchRun.cfg), and its reversed witness MCBenchRunBrokenNoRemove.cfg, a failed copy that skips the deferred remove, must break NothingLeftBehind.

Index

Constants

View Source
const (
	// DefaultRoot is where the run directories are made.
	DefaultRoot = "nova-bench/runs"
	// DefaultCache is the bench's Go build cache, shared by every run on it.
	DefaultCache = "nova-bench/cache/go-build"
	// Nice is the niceness of the command: the bench is shared with the work
	// of every other card on it.
	Nice = 19
	// NoAnswer is ssh's own exit status: it could not reach or log in to the
	// host, so the remote command never ran.
	NoAnswer = 255
	// StepBudget bounds the make and remove steps, which are one short remote
	// command each; the copy and the command itself are bounded only by the
	// caller's context.
	StepBudget = 2 * time.Minute
)

Defaults of the paths on the bench, relative to the login's home.

View Source
const GateRefPrefix = "refs/nova-gate/"

GateRefPrefix is where the lander's temporary refs live on the remote.

View Source
const NoMirror = 3

NoMirror is the stage line's own exit when the bench keeps no mirror at the path.

View Source
const SkipAfter = 2

SkipAfter is how many stage failures in one land pass pass a bench over for the rest of it.

Variables

View Source
var Env = []string{"GOFLAGS=-mod=readonly", "NOVA_TEST_NO_HOST=1"}

Env is the environment the command runs under, after GOCACHE: the same on every bench, so a run is a run wherever it lands.

View Source
var ErrNoBench = errors.New("no bench answered")

ErrNoBench is a run in which no host answered.

View Source
var SSHOptions = []string{"-o", "BatchMode=yes", "-o", "ConnectTimeout=10", "-o", "ForwardAgent=no"}

SSHOptions are on every ssh this package starts: BatchMode so a missing key is a refusal rather than a prompt nobody answers, ConnectTimeout so a sleeping bench costs seconds and the fallback is tried, and no agent forwarded to a bench.

Functions

func CheckHost

func CheckHost(h string) error

CheckHost refuses a bench name ssh could read as an option or as more than one host.

func CheckPath

func CheckPath(flag, p string) error

CheckPath refuses a bench path that is not plain: one the remote shell would read differently from how it is written (~, a blank, a quote), one that climbs (..), or the home or the filesystem root itself.

func CopyLine

func CopyLine(dst string) string

CopyLine is the remote line of the copy step: make dst and unpack stdin there.

func ExecLine

func ExecLine(dir, cache string, argv []string) string

ExecLine is the remote line that runs argv in dir/repo.

func GateRef

func GateRef(key, sha string) string

GateRef is the temporary ref of one gate: key (the batch's stream) made a ref name component, and the sha's first twelve, so two gates never share one.

func MakeLine

func MakeLine(root string) string

MakeLine is the remote line of the make step.

func MirrorDir

func MirrorDir(name string) string

MirrorDir is the bench's mirror of the repository named name, relative to the login's home: the directory swarm.MirrorPath names under the bench's home.

func Quote

func Quote(s string) string

Quote is one word to a POSIX shell.

func RemoveLine

func RemoveLine(dir string) string

RemoveLine is the remote line of the remove step: the one directory made.

func StageLine

func StageLine(s MirrorStage, dst string) string

StageLine is the remote line of the mirror stage into dst: the mirror must exist; the commit is fetched from the mirror's origin (else Remote) by the temporary ref unless the mirror holds it already, no ref or FETCH_HEAD written; then dst is a clone of the mirror borrowing its objects, checked out detached at the sha.

func WithGateRef

func WithGateRef(ctx context.Context, g RefGit, sha, ref string, gate func() error) error

WithGateRef pushes sha to ref, runs gate, and deletes ref whatever gate did, under a context the caller's cancellation does not end (bounded by StepBudget), so an interrupted gate still removes its ref. A push that fails is gate not run, the error saying so; a delete that fails is joined to gate's error.

func WriteTree

func WriteTree(w io.Writer, src string, withGit bool) error

WriteTree writes the tree under src to w as a tar stream, paths relative to src: directories, regular files and symlinks, each with its mode. The .git at the top of the tree is left out unless withGit; anything else that is not one of those three (a socket, a device) is refused, never skipped.

Types

type Exec

type Exec struct{}

Exec is the production transport: the system ssh, and nothing else. The copy is a tar stream this process writes onto ssh's stdin, so no rsync or local tar is needed here and the bench needs only tar. The lander's tree gate does not copy: it stages from the bench's mirror (stage_mirror.go).

func (Exec) Copy

func (e Exec) Copy(ctx context.Context, host, src, dst string, withGit bool, stderr io.Writer) error

Copy copies src's contents into host:dst: dst is made, and the tree goes over as a tar stream on ssh's stdin that the bench's tar unpacks there.

func (Exec) CopySized

func (Exec) CopySized(ctx context.Context, host, src, dst string, withGit bool, stderr io.Writer) (int64, error)

CopySized is Copy, and how many bytes of tar stream it wrote. A refusal names its cause: WriteTree's own (a socket, a device), else the bench's tar exit, whose stderr the caller's stderr receives.

func (Exec) Shell

func (Exec) Shell(ctx context.Context, host, line string, stdout, stderr io.Writer) (int, error)

Shell runs line on host through ssh.

type MirrorStage

type MirrorStage struct {
	// Mirror is the bare mirror on the bench, relative to the login's home or absolute
	// (MirrorDir).
	Mirror string
	// Remote is the URL the ref is fetched from when the mirror names no origin of its
	// own; the mirror's origin is preferred, since that is the URL the bench can read.
	Remote string
	// Ref is the temporary ref the lander pushed (GateRef); Sha the commit it names, the
	// tree the run's command runs in.
	Ref, Sha string
}

MirrorStage is a run's tree staged from the bench's mirror in place of a copy.

func (*MirrorStage) Validate

func (s *MirrorStage) Validate() error

Validate refuses a stage the remote line could read as more than it says.

type Options

type Options struct {
	// Hosts are the benches in the order they are tried: the host, then the
	// fallback. Only a host that does not answer moves the run to the next.
	Hosts []string
	// Dir is the local tree to copy; unread when Stage is set.
	Dir string
	// Stage, when set, stages the tree from the bench's mirror in place of the copy.
	Stage *MirrorStage
	// Root is the directory on the bench the run directory is made in, and
	// Cache is GOCACHE there; each is relative to the login's home or
	// absolute. Empty is the default.
	Root, Cache string
	// WithGit copies the tree's .git as well.
	WithGit bool
	// Argv is the command, run in the copy.
	Argv []string
	// Stdout and Stderr receive the command's output as it arrives; Notes
	// receives the run's own lines (a host passed over).
	Stdout, Stderr, Notes io.Writer
	// Now is the run's clock, for the stage's wall time; nil is the wall's.
	Now func() time.Time
	// Staged, when set, is told the stage as it ends, refused or not, before the
	// command runs: the lander's beat says it while the command runs.
	Staged func(Stage)
}

Options is one run.

func (*Options) Validate

func (o *Options) Validate() error

Validate checks every field Run reads before anything is reached.

type RefGit

type RefGit interface {
	Push(ctx context.Context, sha, ref string) error
	Delete(ctx context.Context, ref string) error
}

RefGit is the lander's side of a mirror stage: the gated commit pushed to a temporary ref on the remote the bench fetches from, and that ref deleted after.

type Result

type Result struct {
	// Host is the bench that ran the command; RunDir the directory made there.
	Host, RunDir string
	// Code is the command's exit status.
	Code int
	// Removed is whether the run directory was removed; RemoveErr says why not.
	Removed   bool
	RemoveErr error
	// Stage is what the copy or the mirror stage did (Stage.Line).
	Stage Stage
}

Result is what a run did.

func Run

func Run(ctx context.Context, t Transport, o Options) (Result, error)

Run is one run: the first host that answers makes a run directory, takes the copy, runs the command and has the directory removed. The error is a run that could not reach the command (no host answered, the copy failed, a step was refused); a command that ran and failed is Result.Code, never an error.

type Stage

type Stage struct {
	// Host is the bench; Via how the tree got there: "mirror" or "tar".
	Host, Via string
	// Bytes is what crossed the wire from this machine (the tar stream; nothing but the
	// line for a mirror stage); Wall the stage's time on the run's clock.
	Bytes int64
	Wall  time.Duration
	// Err is the stage's refusal, nil when the tree is staged.
	Err *StageError
}

Stage is what a run's stage did, for the lander's lines.

func (Stage) Line

func (s Stage) Line() string

Line is the stage as the lander's lines say it: "copy <host> <n>MB <t>s via <via>", or "copy refused: <why>". A run that staged nothing is "".

type StageError

type StageError struct {
	Host, Step string
	Code       int
	Tail       string
	Wall       time.Duration
	Err        error
}

StageError is a stage that did not put the tree on the bench: the step that refused, its exit (0 when it has none), the tail of its stderr and the wall time it took.

func (*StageError) Error

func (e *StageError) Error() string

Error is the refusal's one line: "copy refused: <host> after <t>s: <step>[ exit <n>][: <err>][: <stderr tail>]".

func (*StageError) Unwrap

func (e *StageError) Unwrap() error

type StageSkips

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

StageSkips is one land pass's record of the benches whose stage failed. Safe for the pass's streams at once.

func (*StageSkips) Fail

func (s *StageSkips) Fail(host, why string) (skipped bool)

Fail records one failed stage on host, why its refusal; skipped says host is now passed over for the rest of the pass.

func (*StageSkips) Ring

func (s *StageSkips) Ring(ring []string) (keep, notes []string)

Ring is ring without the hosts passed over this pass, in its order, and one line for each host left out.

func (*StageSkips) Skipped

func (s *StageSkips) Skipped(host string) (string, bool)

Skipped is the reason host is passed over this pass, and whether it is.

type Transport

type Transport interface {
	// Shell runs one line under the login shell on host, streaming its output.
	// The int is the remote status (NoAnswer when ssh itself failed); the error
	// is only for a transport that could not start at all.
	Shell(ctx context.Context, host, line string, stdout, stderr io.Writer) (int, error)
	// Copy copies the contents of the local directory src into dst on host
	// (dst is created; its parent exists), leaving .git out unless withGit.
	Copy(ctx context.Context, host, src, dst string, withGit bool, stderr io.Writer) error
}

Transport is what a run asks of the wire. The production transport is ssh alone (Exec), the copy a tar stream on its stdin; the tests pass a fake that records each call.

Jump to

Keyboard shortcuts

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