summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/ioworkload/scenario_readwrite.go45
-rw-r--r--cmd/ioworkload/scenarios.go2
-rw-r--r--integrationtests/readwrite_test.go42
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