summaryrefslogtreecommitdiff
path: root/cmd/ioworkload
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-06-06 09:05:36 +0300
committerPaul Buetow <paul@buetow.org>2026-06-06 09:05:36 +0300
commit33dfe4ee3cf948444571554aa35508605fee0474 (patch)
tree90e462bb697e55814b644e61d55d10ef155571af /cmd/ioworkload
parent1b4202ac84acd129e274112a8293b4b319af9eb0 (diff)
test(retbytes): assert getdents64 READ_CLASSIFIED byte count end-to-end
getdents64 was listed in retbytesTraceArgs but the retbytes workload never issued it and TestRetbytesPhaseA never asserted its exit byte count, leaving the READ_CLASSIFIED ctx->ret path for getdents/getdents64 unverified end-to-end (unlike sibling read/recvfrom/getxattrat). Add retbytesGetdents to the phase-A workload: it populates a temp directory with several files, opens it O_DIRECTORY, and re-issues getdents64 in a short window (lseek-rewind each iteration) so ior can attach and capture an enter/exit pair under parallel load. A non-empty directory guarantees ctx->ret > 0. Assert enter_getdents64 presence (MinCount) plus bytes>=1 via assertEventBytesAtLeast and a positive duration, mirroring the existing READ-classified siblings. Bytes>=1 (not an exact payload length) because dirent size varies with filename length and alignment. Coverage hardening only; classify.go getdents/getdents64=ReadClassified is correct. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'cmd/ioworkload')
-rw-r--r--cmd/ioworkload/scenario_retbytes.go53
1 files changed, 53 insertions, 0 deletions
diff --git a/cmd/ioworkload/scenario_retbytes.go b/cmd/ioworkload/scenario_retbytes.go
index d9b2984..0382db2 100644
--- a/cmd/ioworkload/scenario_retbytes.go
+++ b/cmd/ioworkload/scenario_retbytes.go
@@ -6,6 +6,7 @@ import (
"path/filepath"
"runtime"
"syscall"
+ "time"
"unsafe"
)
@@ -39,9 +40,61 @@ func retbytesPhaseA() error {
if err := retbytesTee(); err != nil {
return err
}
+ if err := retbytesGetdents(); err != nil {
+ return err
+ }
return retbytesProcessVM()
}
+// retbytesGetdents opens a non-empty directory and reads its entries via
+// getdents64(2). getdents/getdents64 are READ_CLASSIFIED, so a successful call
+// on a populated directory returns ctx->ret > 0 (bytes filled into the dirent
+// buffer). This drives the exit byte-count assertion in TestRetbytesPhaseA.
+func retbytesGetdents() error {
+ dir, cleanup, err := makeTempDir("retbytes-getdents")
+ if err != nil {
+ return err
+ }
+ defer cleanup()
+
+ // Create several files so getdents64 has substantial dirent data to return,
+ // guaranteeing a strictly positive byte count.
+ for i := 0; i < 4; i++ {
+ filePath := filepath.Join(dir, fmt.Sprintf("getdents-file-%d.txt", i))
+ fd, openErr := syscall.Open(filePath, syscall.O_RDWR|syscall.O_CREAT, 0o644)
+ if openErr != nil {
+ return fmt.Errorf("open file: %w", openErr)
+ }
+ syscall.Close(fd)
+ }
+
+ dirFD, err := syscall.Open(dir, syscall.O_RDONLY|syscall.O_DIRECTORY, 0)
+ if err != nil {
+ return fmt.Errorf("open dir: %w", err)
+ }
+ defer syscall.Close(dirFD)
+
+ // Re-issue getdents64 in a short window so ior has enough time to attach and
+ // capture an enter/exit pair under high parallel integration load. Each call
+ // rewinds via lseek so the directory keeps returning entries.
+ buf := make([]byte, 4096)
+ for i := 0; i < 40; i++ {
+ if _, err := syscall.Seek(dirFD, 0, 0); err != nil {
+ return fmt.Errorf("seek dir: %w", err)
+ }
+ n, _, errno := syscall.Syscall(syscall.SYS_GETDENTS64, uintptr(dirFD), uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)))
+ runtime.KeepAlive(buf)
+ if errno != 0 {
+ return fmt.Errorf("getdents64: %w", errno)
+ }
+ if n == 0 {
+ return fmt.Errorf("getdents64 returned 0 bytes on a non-empty directory")
+ }
+ time.Sleep(25 * time.Millisecond)
+ }
+ return nil
+}
+
func retbytesSocketIO() error {
fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
if err != nil {