Test / test (push) Successful in 2m4s
Release / gates (push) Successful in 2m5s
Release / build (amd64, freebsd) (push) Successful in 1m27s
Release / build (amd64, linux) (push) Successful in 1m22s
Release / build (amd64, netbsd) (push) Successful in 1m19s
Release / build (amd64, openbsd) (push) Successful in 1m20s
Release / build (arm64, darwin) (push) Successful in 1m21s
Release / build (arm64, freebsd) (push) Successful in 1m26s
Release / build (arm64, linux) (push) Successful in 1m25s
Release / build (arm64, netbsd) (push) Successful in 1m31s
Release / build (arm64, openbsd) (push) Successful in 1m27s
Release / build (loong64, linux) (push) Successful in 1m37s
Release / build (riscv64, linux) (push) Successful in 1m21s
Release / release (push) Successful in 40s
Assisted-by: GLM 5.3 Flash
203 lines
5.5 KiB
Go
203 lines
5.5 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
// The wire level benchmarks: the server handler and the client library
|
|
// over a loopback connection, so one operation carries the whole stack,
|
|
// RPC and XDR and sessions included, on top of the backend.
|
|
package nfs4server_test
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"sourcedock.dev/petrbalvin/nfs/internal/nfs4"
|
|
"sourcedock.dev/petrbalvin/nfs/internal/nfs4server"
|
|
"sourcedock.dev/petrbalvin/nfs/internal/nfsclient"
|
|
"sourcedock.dev/petrbalvin/nfs/internal/nfsfs"
|
|
"sourcedock.dev/petrbalvin/nfs/internal/server"
|
|
"sourcedock.dev/petrbalvin/nfs/internal/xdr"
|
|
)
|
|
|
|
const wireChunk = 64 << 10
|
|
|
|
// benchWire serves one export over loopback, dials the client library at
|
|
// it and returns the client and the handle of one regular file of the
|
|
// given size. Setup runs once, outside the measured region.
|
|
func benchWire(b *testing.B, fileSize int) (*nfsclient.Client, []byte) {
|
|
b.Helper()
|
|
root := b.TempDir()
|
|
backend, err := nfsfs.NewLocal(root)
|
|
if err != nil {
|
|
b.Fatalf("NewLocal: %v", err)
|
|
}
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
b.Fatalf("Listen: %v", err)
|
|
}
|
|
ctx, stop := context.WithCancel(context.Background())
|
|
srv := &server.Server{Handle: (&nfs4server.Handler{FS: backend}).HandleConn}
|
|
go func() { _ = srv.Serve(ctx, ln) }()
|
|
b.Cleanup(stop)
|
|
|
|
name := "benchfile"
|
|
if fileSize > 0 {
|
|
p := filepath.Join(root, name)
|
|
buf := make([]byte, 1<<20)
|
|
for i := range buf {
|
|
buf[i] = byte(i)
|
|
}
|
|
f, err := os.Create(p)
|
|
if err != nil {
|
|
b.Fatalf("Create: %v", err)
|
|
}
|
|
for written := 0; written < fileSize; written += len(buf) {
|
|
if _, err := f.Write(buf); err != nil {
|
|
b.Fatalf("Write: %v", err)
|
|
}
|
|
}
|
|
if err := f.Close(); err != nil {
|
|
b.Fatalf("Close: %v", err)
|
|
}
|
|
}
|
|
cl, err := nfsclient.Dial(ln.Addr().String())
|
|
if err != nil {
|
|
b.Fatalf("Dial: %v", err)
|
|
}
|
|
b.Cleanup(func() { _ = cl.Close() })
|
|
if err := cl.Establish("bench"); err != nil {
|
|
b.Fatalf("Establish: %v", err)
|
|
}
|
|
res, bodies, err := cl.Compound("bench-lookup", [][]byte{
|
|
nfs4.AppendPutRootfh(nil),
|
|
nfs4.AppendLookup(nil, name),
|
|
nfs4.AppendGetfh(nil),
|
|
})
|
|
if err != nil {
|
|
b.Fatalf("Compound: %v", err)
|
|
}
|
|
if res.Status != nfs4.ErrOK {
|
|
b.Fatalf("lookup status %d", res.Status)
|
|
}
|
|
fh, err := xdr.NewDecoder(bodies[len(bodies)-1]).VarOpaque()
|
|
if err != nil {
|
|
b.Fatalf("decode fh: %v", err)
|
|
}
|
|
return cl, fh
|
|
}
|
|
|
|
// compound runs one COMPOUND and requires NFS4ERR_SUCCESS of it.
|
|
func compound(b *testing.B, cl *nfsclient.Client, tag string, ops [][]byte) [][]byte {
|
|
b.Helper()
|
|
res, bodies, err := cl.Compound(tag, ops)
|
|
if err != nil {
|
|
b.Fatalf("%s: %v", tag, err)
|
|
}
|
|
if res.Status != nfs4.ErrOK {
|
|
b.Fatalf("%s: nfs status %d", tag, res.Status)
|
|
}
|
|
return bodies
|
|
}
|
|
|
|
// BenchmarkWireRead64K reads 64 KiB per COMPOUND from a 64 MiB file over
|
|
// the loopback connection, cycling through the offsets.
|
|
func BenchmarkWireRead64K(b *testing.B) {
|
|
cl, fh := benchWire(b, 64<<20)
|
|
off := uint64(0)
|
|
b.SetBytes(wireChunk)
|
|
b.ResetTimer()
|
|
for b.Loop() {
|
|
bodies := compound(b, cl, "bench-read", [][]byte{
|
|
nfs4.AppendPutfh(nil, fh),
|
|
nfs4.AppendReadArgs(nil, nfs4.Stateid{}, off, wireChunk),
|
|
})
|
|
d := xdr.NewDecoder(bodies[len(bodies)-1])
|
|
if _, err := d.Bool(); err != nil {
|
|
b.Fatalf("decode eof: %v", err)
|
|
}
|
|
if _, err := d.VarOpaque(); err != nil {
|
|
b.Fatalf("decode data: %v", err)
|
|
}
|
|
off += wireChunk
|
|
if off > 64<<20-wireChunk {
|
|
off = 0
|
|
}
|
|
}
|
|
}
|
|
|
|
// BenchmarkWireWrite64K writes 64 KiB per COMPOUND to a preallocated
|
|
// 64 MiB file, cycling through the offsets, one open around the run.
|
|
func BenchmarkWireWrite64K(b *testing.B) {
|
|
cl, _ := benchWire(b, 64<<20)
|
|
bodies := compound(b, cl, "bench-open", [][]byte{
|
|
nfs4.AppendPutRootfh(nil),
|
|
nfs4.AppendOpenArgs(nil, 0, []byte("bench"), nfs4.ShareAccessBoth, 0,
|
|
true, 0o644, "benchfile"),
|
|
nfs4.AppendGetfh(nil),
|
|
})
|
|
fh, err := xdr.NewDecoder(bodies[len(bodies)-1]).VarOpaque()
|
|
if err != nil {
|
|
b.Fatalf("decode fh: %v", err)
|
|
}
|
|
var st nfs4.Stateid
|
|
stateBody := bodies[len(bodies)-2]
|
|
if len(stateBody) < len(st) {
|
|
b.Fatalf("open result carries %d bytes of stateid", len(stateBody))
|
|
}
|
|
copy(st[:], stateBody)
|
|
buf := make([]byte, wireChunk)
|
|
off := uint64(0)
|
|
b.SetBytes(wireChunk)
|
|
b.ResetTimer()
|
|
for b.Loop() {
|
|
compound(b, cl, "bench-write", [][]byte{
|
|
nfs4.AppendPutfh(nil, fh),
|
|
nfs4.AppendWriteArgs(nil, st, off, nfs4.StableFileSync, buf),
|
|
})
|
|
off += wireChunk
|
|
if off > 64<<20-wireChunk {
|
|
off = 0
|
|
}
|
|
}
|
|
b.StopTimer()
|
|
compound(b, cl, "bench-close", [][]byte{
|
|
nfs4.AppendPutfh(nil, fh),
|
|
nfs4.AppendCloseArgs(nil, st),
|
|
})
|
|
}
|
|
|
|
// BenchmarkWireGetattr asks the attributes of one file per COMPOUND.
|
|
func BenchmarkWireGetattr(b *testing.B) {
|
|
cl, fh := benchWire(b, 1<<20)
|
|
request := nfs4.OfBits(nfs4.AttrType, nfs4.AttrSize, nfs4.AttrMode)
|
|
b.ResetTimer()
|
|
for b.Loop() {
|
|
bodies := compound(b, cl, "bench-getattr", [][]byte{
|
|
nfs4.AppendPutfh(nil, fh),
|
|
nfs4.AppendGetattr(nil, request),
|
|
})
|
|
d := xdr.NewDecoder(bodies[len(bodies)-1])
|
|
if _, err := nfs4.ReadBitmap(d); err != nil {
|
|
b.Fatalf("decode bitmap: %v", err)
|
|
}
|
|
if _, err := d.VarOpaque(); err != nil {
|
|
b.Fatalf("decode attrs: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// BenchmarkWireLookup resolves one name under the root per COMPOUND.
|
|
func BenchmarkWireLookup(b *testing.B) {
|
|
cl, _ := benchWire(b, 1<<20)
|
|
b.ResetTimer()
|
|
for b.Loop() {
|
|
compound(b, cl, "bench-lookup", [][]byte{
|
|
nfs4.AppendPutRootfh(nil),
|
|
nfs4.AppendLookup(nil, "benchfile"),
|
|
nfs4.AppendGetfh(nil),
|
|
})
|
|
}
|
|
}
|