diff options
| author | Paul Buetow <paul@buetow.org> | 2026-06-06 09:05:36 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-06-06 09:05:36 +0300 |
| commit | 33dfe4ee3cf948444571554aa35508605fee0474 (patch) | |
| tree | 90e462bb697e55814b644e61d55d10ef155571af /cmd/ioworkload | |
| parent | 1b4202ac84acd129e274112a8293b4b319af9eb0 (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.go | 53 |
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 { |
