CARLOS rastrillo docs

🤖 perftest

amadan.net/rastrillo/rastrillo/perf/perftest

Fails the build when a screen or a cold start is slower than perf's budgets: 150ms to first byte for a screen, 500ms for a cold start. perf reports these numbers in production; perftest checks them in CI. The scaffold's internal/<app>test/perf_test.go calls both functions, and make perf runs it.

Screens

func Screens(t testing.TB, cfg ScreenConfig)

type ScreenConfig struct {
	Routes  chi.Routes
	Handler http.Handler
	Paths   map[string]string
	Expect  map[string]int
	Signin  func(c *http.Client, base string)
	Opaque  []Opaque
}

Screens walks Routes, your chi router, and requests every GET route through Handler, which should be the handler production serves: rastrillo.Handler over your app's options. Each route gets ten requests. The first two are discarded, the median of the rest must be within budget, and no single one may take more than three times the budget.

Time to first byte is measured by the client, so it is what a visitor waits for. A handler that sends its headers early and then streams is judged on when it started answering, the way perf judges it in production.

Each route must answer 200, or the status Expect gives for its pattern. A redirect to sign-in or a 404 fails rather than being timed, so set Signin to sign the test's client in, and give each route with a parameter a real path in Paths ("/notes/{id}": "/notes/1"), whose row your test creates first. A route with a parameter and no path fails.

A route given skip in budgets.txt is not requested. A perf record that names no route fails, since Screens sees every route there is. Call it once per test binary, from a test that is not parallel.

Handlers the router cannot see into

type Opaque struct {
	Patterns []string
	Screens  []OpaqueScreen
}

type OpaqueScreen struct {
	Key    string
	Path   string
	Expect int
}

chi cannot list the routes inside a handler that is not a chi router, such as a generated http.ServeMux mounted at /bookmarks/*. Such a mount fails until you either list its screens in an Opaque group, or record perf GET /bookmarks/* skip with a reason:

var cfg = perftest.ScreenConfig{
	Opaque: []perftest.Opaque{{
		Patterns: []string{"/bookmarks", "/bookmarks/*"},
		Screens: []perftest.OpaqueScreen{
			{Key: "GET /bookmarks/{id}", Path: "/bookmarks/1"},
		},
	}},
}

Key is the screen's name, the one perf logs and a budgets.txt record uses.

Boot

func Boot(t *testing.T, cfg BootConfig)

type BootConfig struct {
	Prepare func(t testing.TB) string
	Build   func(dbPath string) (http.Handler, func(), error)
	Path    string
	Expect  int
}

const BootMarker = "rastrillo-budget/v1 boot-first-byte"

Boot measures a cold start: it starts the test binary again as a new process, which builds your app with Build and requests Path, and times everything from starting the process to the first byte of the response. It does that three times and holds the median to 500ms, or to the boot record in budgets.txt.

Prepare returns a migrated database, and runs once, before any process starts, so the measurement never includes migrating. The scaffold passes schema.Path. Each new process gets its own copy.

The child prints BootMarker when the first byte arrives, and the parent stops its clock when it reads it. A child that has not finished within 10 seconds is stopped, along with anything it started. Call Boot from a top-level test. Redirects are not followed: Path must answer Expect (200 by default) itself.

When it fails

Both functions fail the test only when AMADAN_CI or CI is set. Locally they print every measurement and pass, because a busy laptop says nothing reliable about speed. make perf shows each measurement either way.

Read this page as markdown — exact, unstyled, and cheap for an agent to fetch.