diff options
| -rw-r--r-- | cmd/ioworkload/scenario_readwrite.go | 45 | ||||
| -rw-r--r-- | cmd/ioworkload/scenarios.go | 2 | ||||
| -rw-r--r-- | integrationtests/readwrite_test.go | 42 |
3 files changed, 89 insertions, 0 deletions
diff --git a/cmd/ioworkload/scenario_readwrite.go b/cmd/ioworkload/scenario_readwrite.go index c69e588..02fab53 100644 --- a/cmd/ioworkload/scenario_readwrite.go +++ b/cmd/ioworkload/scenario_readwrite.go @@ -251,6 +251,51 @@ func readwriteWritev() error { return nil } +// readwriteReadahead opens a file, writes data, then calls readahead(2) on it. +// readahead(fd, offset, count) initiates non-blocking readahead so subsequent +// reads are served from the page cache. Despite its ssize_t prototype it returns +// 0 on success / -1 on error (it does NOT return a byte count and transfers no +// bytes to userspace), so ior classifies it KindFd / UNCLASSIFIED. The scenario +// exercises the enter fd_event (fd at args[0]) and the exit ret_event end-to-end. +func readwriteReadahead() error { + dir, cleanup, err := makeTempDir("readwrite-readahead") + if err != nil { + return err + } + defer cleanup() + + path := filepath.Join(dir, "readaheadfile.txt") + fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CREAT, 0o644) + if err != nil { + return fmt.Errorf("open: %w", err) + } + defer syscall.Close(fd) + + if _, err := syscall.Write(fd, []byte("readahead test data")); err != nil { + return fmt.Errorf("write: %w", err) + } + + // readahead(fd, offset=0, count=4096): prime the page cache for the file. + _, _, errno := syscall.Syscall(syscall.SYS_READAHEAD, uintptr(fd), 0, 4096) + if errno != 0 { + return fmt.Errorf("readahead: %w", errno) + } + return nil +} + +// readwriteReadaheadEbadf calls readahead(2) on an invalid fd. +// The syscall fails with EBADF, but ior captures the enter_readahead tracepoint +// because arguments are read on syscall entry before the kernel returns an error. +func readwriteReadaheadEbadf() error { + for i := 0; i < 5; i++ { + _, _, errno := syscall.Syscall(syscall.SYS_READAHEAD, 99999, 0, 4096) + if errno == 0 { + return fmt.Errorf("expected EBADF, but readahead succeeded") + } + } + return nil +} + // readwriteWronlyRead opens a file O_WRONLY, then attempts to read from it. // The read fails with EBADF, but ior should capture the enter_read tracepoint // because arguments are read on syscall entry before the kernel returns an error. diff --git a/cmd/ioworkload/scenarios.go b/cmd/ioworkload/scenarios.go index 6503db7..000750a 100644 --- a/cmd/ioworkload/scenarios.go +++ b/cmd/ioworkload/scenarios.go @@ -27,6 +27,8 @@ var scenarios = map[string]func() error{ "readwrite-rdonly-write": readwriteRdonlyWrite, "readwrite-pread-invalid": readwritePreadInvalid, "readwrite-pwrite-invalid": readwritePwriteInvalid, + "readwrite-readahead": readwriteReadahead, + "readwrite-readahead-ebadf": readwriteReadaheadEbadf, "retbytes-phase-a": retbytesPhaseA, "socket-basic": socketBasic, "socketpair-basic": socketpairBasic, diff --git a/integrationtests/readwrite_test.go b/integrationtests/readwrite_test.go index 7a479b7..c1efbb4 100644 --- a/integrationtests/readwrite_test.go +++ b/integrationtests/readwrite_test.go @@ -221,6 +221,48 @@ func TestReadwritePwriteInvalid(t *testing.T) { }) } +func TestReadwriteReadahead(t *testing.T) { + // readahead(2) is KindFd / UNCLASSIFIED: despite its ssize_t prototype it + // returns 0/-1 and transfers no bytes to userspace, so the tracer must + // attribute zero bytes (not misread the 0/-1 return as a byte count) while + // still capturing the fd (args[0]) on enter and timing the syscall. + result, _ := runScenarioResult(t, "readwrite-readahead", []ExpectedEvent{ + { + PathContains: "readaheadfile.txt", + Tracepoint: "enter_readahead", + Comm: "ioworkload", + MinCount: 1, + }, + }) + exp := ExpectedEvent{ + PathContains: "readaheadfile.txt", + Tracepoint: "enter_readahead", + Comm: "ioworkload", + } + // UNCLASSIFIED: no byte count is attributed for a successful readahead. + assertEventBytesEqual(t, result, exp, 0) + // Timing is captured end-to-end (enter/exit paired into a duration). + assertEventDurationPositive(t, result, exp) +} + +func TestReadwriteReadaheadEbadf(t *testing.T) { + // readahead on an invalid fd fails with EBADF, but ior still captures the + // enter_readahead tracepoint because arguments are read on syscall entry + // before the kernel returns the error. The UNCLASSIFIED -1 return must not + // be attributed as bytes. + result, _ := runScenarioResult(t, "readwrite-readahead-ebadf", []ExpectedEvent{ + { + Tracepoint: "enter_readahead", + Comm: "ioworkload", + MinCount: 1, + }, + }) + assertEventBytesEqual(t, result, ExpectedEvent{ + Tracepoint: "enter_readahead", + Comm: "ioworkload", + }, 0) +} + func assertEventBytesAtLeast(t *testing.T, result TestResult, exp ExpectedEvent, minBytes uint64) { t.Helper() var matched bool |
