slowtests

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Sep 28, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package slowtests is the machine behind cmd/nova-ci's `slowtests` verb in docs/SPEC-CI.md, "The per-package test time budget". It reads the newline-delimited TestEvent objects `go test -json` writes, sums the package-level Elapsed for each package, keeps the slowest few test-level rows so a finding can name where the time went, and reports every package whose total is over the budget. It reads and writes no file and never touches the network or the clock: the events and the budget come from the caller, so a test feeds canned lines and asserts a verdict.

Index

Constants

View Source
const MaxHeadroom = 3.0

MaxHeadroom is the most a row's budget may exceed its own measurement: three times, the ratio run 36261817989 (2026-09-26) put on a busy Studio against the same tests idle is 1.0-1.2 s over 0.9 s, well inside it.

View Source
const SleepsMarker = "SLEEPS:"

SleepsMarker is the text a unit test's t.Skip starts with when it is skipped for a sleep or a wall-clock wait (nova-tools #4221); go test -json carries it as an output event of the skipped test.

Variables

View Source
var Benches = []string{"space", "studio", "superman", "batman", "air"}

Benches are the machines a row may name as where it was measured: the self-hosted runner groups and hosts ci.yml's test legs run on (space, and the studio group over air, batman, studio and superman). internal/ci's TestMeasuredBenchesAreCIRunners holds each to ci.yml.

Functions

func Seconds

func Seconds(seconds float64) string

Seconds renders a duration in seconds to one decimal, with the unit: 3.2 becomes "3.2s" and 70.0 stays "70.0s", so two adjacent rows compare without a reader doing arithmetic.

func Verdict

func Verdict(r Report, load Load, enforce bool, ledger string) ([]string, int)

Verdict is what the check prints and its exit code. Every CI-SLOW line and the CI-LOAD line are printed on every leg, so the time is always measured. The exit code never reads the load:

  • an unledgered SLEEPS skip (a CI-SLEEPS line) is 2 on every leg: it is what the test does, a static fact;
  • a CI-SLOW line is 2 only when enforce is set, which one caller does: the nightly whole-tree run on the idle reference leg (ci.yml's schedule branch of the test step, `make test SLOWTESTS_ENFORCE=1`). Everywhere else it is a printed measurement and exit 0.

Types

type Budgets

type Budgets struct {
	Package float64
	Test    float64
	Rows    []Row
	Sleeps  []SleepRow
}

Budgets is what Judge holds a run to. Package is the default seconds for a package's total; Test is the default seconds for one top-level test, zero meaning tests are not judged one by one. Rows are the allowlist: a row whose Test is empty is a package's own budget, any other row one test's. Sleeps is the ledger of the tests already skipped with the SLEEPS marker; a SLEEPS skip not on it is a finding whatever the budgets or the load.

type Event

type Event struct {
	Action  string  `json:"Action"`
	Package string  `json:"Package"`
	Test    string  `json:"Test"`
	Elapsed float64 `json:"Elapsed"`
	Output  string  `json:"Output"`
}

Event is one decoded TestEvent line. The field names are `go test -json`'s own; a package-level event has Test == "" and carries the package's total Elapsed, while a test-level event carries that one test's Elapsed. Output is the text of an output (or build-output) event: the budget check never reads it, and `nova-ci local` prints it under a red test so the failure is named with its own words.

func Parse

func Parse(r io.Reader) ([]Event, error)

Parse decodes newline-delimited TestEvent JSON. Blank lines are skipped; a line that is not an object is an error naming its 1-based line, never a silent skip, so a truncated pipe cannot read as a clean run.

type Load

type Load struct {
	Avg   float64
	CPUs  int
	Known bool
	Why   string
}

Load is the host's run-queue load average and its logical CPU count when a run is judged. It is a MEASUREMENT printed beside the times, never an input to the verdict: a budget verdict is the same on any machine (Rowan's ruling on nova-tools#4413, 2026-09-26), so the load only tells a reader what the box was doing while the times were taken. Known is false when the host has no load average to read (Windows) or the read failed; Why then says so.

func (Load) LoadLine

func (l Load) LoadLine() string

LoadLine is the CI-LOAD line every run prints: the load the times were taken at, or why it is unknown. It carries no verdict.

func (Load) PerCPU

func (l Load) PerCPU() float64

PerCPU is the load over the CPUs, or 0 when unknown.

type OverTest

type OverTest struct {
	Package string
	Name    string
	Seconds float64
	Budget  float64
}

OverTest is one top-level test over its budget: the default per-test budget, or its allowlist row.

type Package

type Package struct {
	Name    string
	Seconds float64
	Slowest []Test
	Budget  float64
}

Package is one package's summed elapsed time and its slowest tests, sorted worst first and capped at testsKept. Budget is the seconds it was judged against: the default, or its allowlist row.

type Report

type Report struct {
	Packages  int
	Over      []Package
	OverTests []OverTest
	Sleepers  []Sleeper
	Slowest   Package
	Budget    time.Duration
}

Report is one run of the budget check: how many packages were seen, the ones over budget (worst first), the tests over budget (worst first), the single slowest package overall, and the package budget they were judged against (kept only so a finding can print it).

func Judge

func Judge(events []Event, b Budgets) Report

Judge folds the events into a report. A package's total is the sum of its package-level Elapsed (Test == ""), judged against its allowlist row or b.Package. With b.Test > 0 every top-level test (no "/" in its name: a subtest's time is already its parent's) is judged against its row or b.Test. Over-budget packages and tests are returned worst first, ties by name, so the output never depends on map iteration. A test that skips with the SLEEPS marker in its output and is not on b.Sleeps is a Sleeper, in first-seen order, one per top-level test.

func Sum

func Sum(events []Event, budget time.Duration) Report

Sum folds the events into a report against one package budget and no per-test budget: Judge with Budgets{Package: budget}.

func (Report) ExitCode

func (r Report) ExitCode() int

ExitCode is the enforced verdict (the nightly leg's): 2 when any package or test is over budget or any test is an unledgered SLEEPS skip, 0 when none is. Verdict is what a leg exits with.

func (Report) OKLine

func (r Report) OKLine() string

OKLine is the one line a run inside budget prints, naming the single slowest package overall. With no packages at all the slot is "none", not empty.

func (Report) OverLines

func (r Report) OverLines() []string

OverLines is one line per over-budget package, worst first, then one per over-budget test, worst first.

func (Report) SleepsLines

func (r Report) SleepsLines(ledger string) []string

SleepsLines is one line per unledgered SLEEPS skip. It is red on every leg: a test skipped for a wall-clock wait is what the test does, not how busy the runner was.

type Row

type Row struct {
	Package  string
	Test     string
	Seconds  float64
	Measured float64
	Where    string
}

Row is one allowlist line: `pkg<TAB>test<TAB>seconds<TAB>measured`, with `-` in the test column for a package's own row. Package is module-relative (internal/ci) and matches an event's import path by its trailing path elements. Seconds is the budget the row enforces; Measured is the time the row was cut from and Where the CI run (`run<id>`) or bench (Benches) that measured it, so no number on the list is a guess: the measured column is `<seconds>s@<where>`, and the budget may not exceed MaxHeadroom times the measurement.

func ParseAllowlist

func ParseAllowlist(r io.Reader) ([]Row, error)

ParseAllowlist reads `pkg<TAB>test<TAB>seconds<TAB>measured` rows. Blank lines and lines starting with # are skipped; `-` in the test column is the package's own row. The measured column is `<seconds>s@<where>`: the time the row was cut from and where it was measured. A malformed row, a budget that is not a positive number, a row with no measurement, a budget under its measurement or over MaxHeadroom times it, or a row written twice is an error naming its 1-based line.

type SleepRow

type SleepRow struct {
	Package string
	Test    string
	Where   string
}

SleepRow is one line of the SLEEPS ledger: `pkg<TAB>test<TAB>where`, a top-level test that skips itself with the SLEEPS marker because it waits on the wall clock, and the run or issue that found the wait.

func ParseSleeps

func ParseSleeps(r io.Reader) ([]SleepRow, error)

ParseSleeps reads the SLEEPS ledger: `pkg<TAB>test<TAB>where` rows, blank lines and # comments skipped. A malformed row or a row written twice is an error naming its 1-based line.

type Sleeper

type Sleeper struct {
	Package string
	Name    string
}

Sleeper is one test skipped with the SLEEPS marker that the ledger does not name.

type Test

type Test struct {
	Name    string
	Seconds float64
}

Test is one test-level row kept for a package's slowest list.

Jump to

Keyboard shortcuts

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