summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/ioworkload/scenario_retbytes.go53
-rw-r--r--integrationtests/retbytes_test.go9
2 files changed, 62 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 {
diff --git a/integrationtests/retbytes_test.go b/integrationtests/retbytes_test.go
index 4baed9e..37eb45b 100644
--- a/integrationtests/retbytes_test.go
+++ b/integrationtests/retbytes_test.go
@@ -19,6 +19,7 @@ func TestRetbytesPhaseA(t *testing.T) {
{Tracepoint: "enter_tee", Comm: "ioworkload", MinCount: 1},
{Tracepoint: "enter_process_vm_writev", Comm: "ioworkload", MinCount: 1},
{Tracepoint: "enter_process_vm_readv", Comm: "ioworkload", MinCount: 1},
+ {Tracepoint: "enter_getdents64", Comm: "ioworkload", MinCount: 1},
}, retbytesTraceArgs)
for _, tracepoint := range []string{
@@ -42,4 +43,12 @@ func TestRetbytesPhaseA(t *testing.T) {
assertEventBytesEqual(t, result, exp, 0)
assertEventDurationPositive(t, result, exp)
}
+
+ // getdents64 is READ_CLASSIFIED: a successful call on a non-empty directory
+ // fills the dirent buffer and reports ctx->ret > 0. Dirent size varies with
+ // filename length and alignment, so assert a conservative bytes>=1 minimum
+ // rather than an exact payload length.
+ getdentsExp := ExpectedEvent{Tracepoint: "enter_getdents64", Comm: "ioworkload"}
+ assertEventBytesAtLeast(t, result, getdentsExp, 1)
+ assertEventDurationPositive(t, result, getdentsExp)
}