summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-06-01 10:31:53 +0300
committerPaul Buetow <paul@buetow.org>2026-06-01 10:31:53 +0300
commit8549884d1d957821b75dfbd5a4ff746667095f17 (patch)
treec486c6643a86beceafd428a7e8b2d312162426f1
parent1b59bbe42c13a2a60667dff51dc02e0c350434d7 (diff)
test(integration): add pwritev/pwritev2 retbytes coverage
Add write-side positional vectored coverage to close the remaining gap in the pwrite64-family byte-accounting validation. The retbytes/readwrite integration suite already exercised pwrite64 (scalar) and the read-side preadv/preadv2, but the WRITE_CLASSIFIED byte attribution for the vectored positional writes pwritev/pwritev2 was only covered by unit tests, not end-to-end. New ioworkload scenarios: - readwrite-pwritev: issues pwritev (syscall.SYS_PWRITEV) writing a known two-iovec payload at offset 0 to a temp file. - readwrite-pwritev2: issues pwritev2 via the explicit syscall number (328 amd64 / 287 arm64, mirroring preadv2SyscallNr) with offset 0 and no flags. New integration tests assert enter_pwritev/enter_pwritev2 fired and that the attributed retbytes equal the exact iovec total, validating WRITE_CLASSIFIED end-to-end. Both pass as root. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
-rw-r--r--cmd/ioworkload/scenario_readwrite.go87
-rw-r--r--cmd/ioworkload/scenarios.go2
-rw-r--r--integrationtests/readwrite_test.go39
3 files changed, 128 insertions, 0 deletions
diff --git a/cmd/ioworkload/scenario_readwrite.go b/cmd/ioworkload/scenario_readwrite.go
index 02fab53..04ee86f 100644
--- a/cmd/ioworkload/scenario_readwrite.go
+++ b/cmd/ioworkload/scenario_readwrite.go
@@ -183,6 +183,93 @@ func readwritePwrite() error {
return nil
}
+// readwritePwritev opens a file and writes data via pwritev (positional
+// vectored write). pwritev returns the number of bytes written and is
+// WRITE_CLASSIFIED, so the scenario writes a known iovec total to validate
+// end-to-end byte attribution for the positional vectored write variant.
+func readwritePwritev() error {
+ dir, cleanup, err := makeTempDir("readwrite-pwritev")
+ if err != nil {
+ return err
+ }
+ defer cleanup()
+
+ path := filepath.Join(dir, "pwritevfile.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)
+
+ buf1 := []byte("pwritev ")
+ buf2 := []byte("test data")
+ iovs := []syscall.Iovec{
+ {Base: &buf1[0], Len: uint64(len(buf1))},
+ {Base: &buf2[0], Len: uint64(len(buf2))},
+ }
+ // pwritev(fd, iov, iovcnt, offset) writes at the given offset (0) without
+ // changing the file position.
+ _, _, errno := syscall.Syscall6(syscall.SYS_PWRITEV, uintptr(fd), uintptr(unsafe.Pointer(&iovs[0])), uintptr(len(iovs)), 0, 0, 0)
+ runtime.KeepAlive(buf1)
+ runtime.KeepAlive(buf2)
+ if errno != 0 {
+ return fmt.Errorf("pwritev: %w", errno)
+ }
+ return nil
+}
+
+// readwritePwritev2 opens a file and writes data via pwritev2 (positional
+// vectored write with flags). Like pwritev it returns the bytes written and is
+// WRITE_CLASSIFIED; the scenario writes a known iovec total to validate
+// end-to-end byte attribution.
+func readwritePwritev2() error {
+ dir, cleanup, err := makeTempDir("readwrite-pwritev2")
+ if err != nil {
+ return err
+ }
+ defer cleanup()
+
+ path := filepath.Join(dir, "pwritev2file.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)
+
+ buf1 := []byte("pwritev2 ")
+ buf2 := []byte("test data")
+ iovs := []syscall.Iovec{
+ {Base: &buf1[0], Len: uint64(len(buf1))},
+ {Base: &buf2[0], Len: uint64(len(buf2))},
+ }
+ nr, err := pwritev2SyscallNr(runtime.GOARCH)
+ if err != nil {
+ return err
+ }
+ // pwritev2(fd, iov, iovcnt, pos_l, pos_h, flags): offset 0, no flags.
+ _, _, errno := syscall.Syscall6(nr, uintptr(fd), uintptr(unsafe.Pointer(&iovs[0])), uintptr(len(iovs)), 0, 0, 0)
+ runtime.KeepAlive(buf1)
+ runtime.KeepAlive(buf2)
+ if errno != 0 {
+ return fmt.Errorf("pwritev2: %w", errno)
+ }
+ return nil
+}
+
+// pwritev2SyscallNr returns the pwritev2(2) syscall number for the given GOARCH.
+// Go's syscall package lacks a SYS_PWRITEV2 constant, so the architecture
+// numbers are provided explicitly (mirroring preadv2SyscallNr).
+func pwritev2SyscallNr(arch string) (uintptr, error) {
+ switch arch {
+ case "amd64":
+ return 328, nil
+ case "arm64":
+ return 287, nil
+ default:
+ return 0, fmt.Errorf("pwritev2 syscall number not defined for GOARCH=%s", arch)
+ }
+}
+
// readwriteReadv opens a file, writes data, then reads it back via readv.
func readwriteReadv() error {
dir, cleanup, err := makeTempDir("readwrite-readv")
diff --git a/cmd/ioworkload/scenarios.go b/cmd/ioworkload/scenarios.go
index 000750a..597d0e8 100644
--- a/cmd/ioworkload/scenarios.go
+++ b/cmd/ioworkload/scenarios.go
@@ -21,6 +21,8 @@ var scenarios = map[string]func() error{
"readwrite-preadv": readwritePreadv,
"readwrite-preadv2": readwritePreadv2,
"readwrite-pwrite": readwritePwrite,
+ "readwrite-pwritev": readwritePwritev,
+ "readwrite-pwritev2": readwritePwritev2,
"readwrite-readv": readwriteReadv,
"readwrite-writev": readwriteWritev,
"readwrite-wronly-read": readwriteWronlyRead,
diff --git a/integrationtests/readwrite_test.go b/integrationtests/readwrite_test.go
index c1efbb4..f4f528a 100644
--- a/integrationtests/readwrite_test.go
+++ b/integrationtests/readwrite_test.go
@@ -66,6 +66,45 @@ func TestReadwritePwrite(t *testing.T) {
}, 1)
}
+func TestReadwritePwritev(t *testing.T) {
+ // pwritev is the positional vectored write sibling of pwrite64/writev and is
+ // WRITE_CLASSIFIED, so a successful write must attribute the iovec total
+ // (sum of both buffers) end-to-end.
+ const payloadLen = uint64(len("pwritev ") + len("test data"))
+ result, _ := runScenarioResult(t, "readwrite-pwritev", []ExpectedEvent{
+ {
+ PathContains: "pwritevfile.txt",
+ Tracepoint: "enter_pwritev",
+ Comm: "ioworkload",
+ MinCount: 1,
+ },
+ })
+ assertEventBytesAtLeast(t, result, ExpectedEvent{
+ PathContains: "pwritevfile.txt",
+ Tracepoint: "enter_pwritev",
+ Comm: "ioworkload",
+ }, payloadLen)
+}
+
+func TestReadwritePwritev2(t *testing.T) {
+ // pwritev2 is the positional vectored write variant with flags; like pwritev
+ // it is WRITE_CLASSIFIED and must attribute the iovec total end-to-end.
+ const payloadLen = uint64(len("pwritev2 ") + len("test data"))
+ result, _ := runScenarioResult(t, "readwrite-pwritev2", []ExpectedEvent{
+ {
+ PathContains: "pwritev2file.txt",
+ Tracepoint: "enter_pwritev2",
+ Comm: "ioworkload",
+ MinCount: 1,
+ },
+ })
+ assertEventBytesAtLeast(t, result, ExpectedEvent{
+ PathContains: "pwritev2file.txt",
+ Tracepoint: "enter_pwritev2",
+ Comm: "ioworkload",
+ }, payloadLen)
+}
+
func TestReadwritePreadv(t *testing.T) {
// preadv is the positional vectored read sibling of pread64/readv and is
// READ_CLASSIFIED, so a successful read must attribute the payload bytes