check / check (push) Failing after 3s
script/loadtest [duration [clients]], or make loadtest, is a benchmark that script/check does not run. It builds the image and vegeta from a pinned commit, then for each scenario starts a new pixad container and a new cmd/loadtest-origin container, an upstream host that answers every path with one generated JPEG: a cached image (hit), a new source image per request (miss), and each new source image asked for by all clients at once (herd). It prints vegeta's report, pixad's peak resident memory and the requests the origin got. The containers share a network in 203.0.113.0/24, as pixad refuses private and local upstream addresses. README.md says how to run and read it; TODO.md records a small baseline. Model: opus-5-5
85 lines
1.9 KiB
Go
85 lines
1.9 KiB
Go
// Command loadtest-origin is the upstream host script/loadtest points pixad
|
|
// at. It answers every request, whatever its path, with the same generated
|
|
// JPEG, so each new path is a new source image for pixad to fetch, and it logs
|
|
// one line per request, so its log counts pixad's fetches.
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"image"
|
|
"image/color"
|
|
"image/jpeg"
|
|
"log/slog"
|
|
"math"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
listenAddress = ":80"
|
|
readHeaderTimeout = 10 * time.Second
|
|
imageWidth = 1600
|
|
imageHeight = 1200
|
|
jpegQuality = 85
|
|
)
|
|
|
|
func main() {
|
|
photo, err := makeJPEG()
|
|
if err != nil {
|
|
slog.Error("cannot make the image", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
server := &http.Server{
|
|
Addr: listenAddress,
|
|
Handler: newHandler(photo),
|
|
ReadHeaderTimeout: readHeaderTimeout,
|
|
}
|
|
|
|
err = server.ListenAndServe()
|
|
slog.Error("server stopped", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// newHandler answers every request with photo and logs the request's path.
|
|
func newHandler(photo []byte) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
slog.Info("request", "path", r.URL.Path)
|
|
w.Header().Set("Content-Type", "image/jpeg")
|
|
_, _ = w.Write(photo)
|
|
})
|
|
}
|
|
|
|
// makeJPEG draws colour gradients crossed with a fine pattern, so the image
|
|
// has detail to decode and does not compress to almost nothing.
|
|
func makeJPEG() ([]byte, error) {
|
|
img := image.NewRGBA(image.Rect(0, 0, imageWidth, imageHeight))
|
|
|
|
// red and green count up from 0 to 255 and wrap around, along each row
|
|
// and down the image.
|
|
var green uint8
|
|
|
|
for y := range imageHeight {
|
|
var red uint8
|
|
|
|
for x := range imageWidth {
|
|
img.SetRGBA(x, y, color.RGBA{
|
|
R: red, G: green, B: red ^ green, A: math.MaxUint8,
|
|
})
|
|
red++
|
|
}
|
|
|
|
green++
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
|
|
err := jpeg.Encode(&buf, img, &jpeg.Options{Quality: jpegQuality})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return buf.Bytes(), nil
|
|
}
|