feat: full NFSv4.2 server and client in pure Go
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
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
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
// 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),
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user