// Copyright (c) 2026 Petr BalvĂ­n (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), }) } }