bench

package
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 8, 2026 License: MIT Imports: 16 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.
  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.

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 MakeLine

func MakeLine(root string) string

MakeLine is the remote line of the make step.

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 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.

func (Exec) Copy

func (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) Shell

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

Shell runs line on host through ssh.

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.
	Dir string
	// 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
}

Options is one run.

func (*Options) Validate

func (o *Options) Validate() error

Validate checks every field Run reads before anything is reached.

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
}

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 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