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,168 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package nfsfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const benchChunk = 64 << 10
|
||||
|
||||
// benchRoot builds a backend over a fresh directory with one file of the
|
||||
// given size, filled with a repeating pattern, and returns the backend
|
||||
// and the file handle. Setup runs once, outside the measured region.
|
||||
func benchRoot(b *testing.B, size int) (*Local, Handle) {
|
||||
b.Helper()
|
||||
root := b.TempDir()
|
||||
l, err := NewLocal(root)
|
||||
if err != nil {
|
||||
b.Fatalf("NewLocal: %v", err)
|
||||
}
|
||||
p := filepath.Join(root, "file")
|
||||
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 < size; 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)
|
||||
}
|
||||
rh, err := l.Root()
|
||||
if err != nil {
|
||||
b.Fatalf("Root: %v", err)
|
||||
}
|
||||
h, _, err := l.Lookup(rh, "file")
|
||||
if err != nil {
|
||||
b.Fatalf("Lookup: %v", err)
|
||||
}
|
||||
return l, h
|
||||
}
|
||||
|
||||
// BenchmarkRead64K reads 64 KiB at a time from a 64 MiB file, cycling
|
||||
// through the offsets so every read touches pages the previous read left.
|
||||
func BenchmarkRead64K(b *testing.B) {
|
||||
l, h := benchRoot(b, 64<<20)
|
||||
off := int64(0)
|
||||
b.SetBytes(benchChunk)
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
if _, err := l.Read(h, off, benchChunk); err != nil {
|
||||
b.Fatalf("Read: %v", err)
|
||||
}
|
||||
off += benchChunk
|
||||
if off > 64<<20-benchChunk {
|
||||
off = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkWrite64K writes 64 KiB at a time over a preallocated 64 MiB
|
||||
// file, cycling through the offsets, so no read has to grow the file.
|
||||
func BenchmarkWrite64K(b *testing.B) {
|
||||
l, h := benchRoot(b, 64<<20)
|
||||
buf := make([]byte, benchChunk)
|
||||
off := int64(0)
|
||||
b.SetBytes(benchChunk)
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
if _, err := l.Write(h, off, buf); err != nil {
|
||||
b.Fatalf("Write: %v", err)
|
||||
}
|
||||
off += benchChunk
|
||||
if off > 64<<20-benchChunk {
|
||||
off = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkGetattr reports the attributes of one file.
|
||||
func BenchmarkGetattr(b *testing.B) {
|
||||
l, h := benchRoot(b, 1<<20)
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
if _, err := l.Getattr(h); err != nil {
|
||||
b.Fatalf("Getattr: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkLookup resolves one name under the export root of a directory
|
||||
// holding a hundred files.
|
||||
func BenchmarkLookup(b *testing.B) {
|
||||
root := b.TempDir()
|
||||
l, err := NewLocal(root)
|
||||
if err != nil {
|
||||
b.Fatalf("NewLocal: %v", err)
|
||||
}
|
||||
rh, err := l.Root()
|
||||
if err != nil {
|
||||
b.Fatalf("Root: %v", err)
|
||||
}
|
||||
for i := range 100 {
|
||||
name := fmt.Sprintf("f%d", i)
|
||||
if err := os.WriteFile(filepath.Join(root, name), []byte("x"), 0o644); err != nil {
|
||||
b.Fatalf("WriteFile: %v", err)
|
||||
}
|
||||
}
|
||||
b.ResetTimer()
|
||||
i := 0
|
||||
for b.Loop() {
|
||||
if _, _, err := l.Lookup(rh, fmt.Sprintf("f%d", i%100)); err != nil {
|
||||
b.Fatalf("Lookup: %v", err)
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkReadDirPage64 pages a 10 000 entry directory 64 entries at a
|
||||
// time after the first page: with the listing cache the cost of a page
|
||||
// is the page, and the benchmark holds that to the measurement.
|
||||
func BenchmarkReadDirPage64(b *testing.B) {
|
||||
root := b.TempDir()
|
||||
l, err := NewLocal(root)
|
||||
if err != nil {
|
||||
b.Fatalf("NewLocal: %v", err)
|
||||
}
|
||||
dir := filepath.Join(root, "big")
|
||||
if err := os.Mkdir(dir, 0o755); err != nil {
|
||||
b.Fatalf("Mkdir: %v", err)
|
||||
}
|
||||
for i := range 10000 {
|
||||
if err := os.WriteFile(filepath.Join(dir, fmt.Sprintf("f%04d", i)), []byte("x"), 0o644); err != nil {
|
||||
b.Fatalf("WriteFile: %v", err)
|
||||
}
|
||||
}
|
||||
rh, err := l.Root()
|
||||
if err != nil {
|
||||
b.Fatalf("Root: %v", err)
|
||||
}
|
||||
h, _, err := l.Lookup(rh, "big")
|
||||
if err != nil {
|
||||
b.Fatalf("Lookup: %v", err)
|
||||
}
|
||||
if _, err := l.ReadDir(h, 0, 64); err != nil {
|
||||
b.Fatalf("first page: %v", err)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
page, err := l.ReadDir(h, 0, 64)
|
||||
if err != nil {
|
||||
b.Fatalf("ReadDir: %v", err)
|
||||
}
|
||||
if len(page.Entries) != 64 || page.EOF {
|
||||
b.Fatalf("page: %d entries, eof %v", len(page.Entries), page.EOF)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user