summaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-31 10:00:22 +0300
committerPaul Buetow <paul@buetow.org>2026-05-31 10:00:22 +0300
commitc6573c61435a23194087558378be3efc1a47fdc4 (patch)
treec88e83dc0a0b29223236ee2042f13bfbb27d1106 /cmd
parente8948e9f52235cbbbd8af098bc624bc20e173e6c (diff)
test(aio): add io_submit end-to-end integration coverage
Audit of io_submit tracing (task 0v) confirmed the tracer is correct by inspection: KindNull (sys_enter_io_ prefix rule) so ctx_id/nr/iocbpp are opaque and no fd/path is captured; FamilyAIO; return is UNCLASSIFIED (the return is a count of iocbs submitted, not a byte count, so it must not inflate READ/WRITE/TRANSFER totals). Enter/exit are paired and timed. No implementation discrepancy and no docs drift. Add a genuine end-to-end test: new aio-submit ioworkload scenario sets up an AIO context and submits one real IOCB_CMD_PWRITE iocb against a temp file via raw syscalls, then tears the context down. TestAioSubmit asserts the enter_io_submit tracepoint fires for the AIO family workload. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'cmd')
-rw-r--r--cmd/ioworkload/scenario_aio.go88
-rw-r--r--cmd/ioworkload/scenarios.go1
2 files changed, 89 insertions, 0 deletions
diff --git a/cmd/ioworkload/scenario_aio.go b/cmd/ioworkload/scenario_aio.go
index 389e734..61bd1ff 100644
--- a/cmd/ioworkload/scenario_aio.go
+++ b/cmd/ioworkload/scenario_aio.go
@@ -2,6 +2,8 @@ package main
import (
"fmt"
+ "os"
+ "path/filepath"
"runtime"
"syscall"
"unsafe"
@@ -14,13 +16,37 @@ import (
const (
sysIoSetup = 206
sysIoDestroy = 207
+ sysIoSubmit = 209
// aioMaxEvents is the nr_events count requested from io_setup(2). It is a
// plain count (NOT an fd), so the tracer must classify the enter event as
// KindNull and capture no fd/path argument.
aioMaxEvents = 32
+
+ // iocbCmdPwrite is IOCB_CMD_PWRITE from <linux/aio_abi.h>: the iocb opcode
+ // requesting a positional write. io_submit submits iocbs carrying this
+ // opcode; the tracer captures none of the iocb contents.
+ iocbCmdPwrite = 1
)
+// iocb mirrors struct iocb from <linux/aio_abi.h> on x86_64 (64 bytes). It is
+// the control block io_submit(2) consumes via its iocbpp pointer-array argument.
+// The tracer treats io_submit's args (ctx_id, nr, iocbpp) as opaque (KindNull),
+// so this layout matters only for driving a real submission, not for tracing.
+type iocb struct {
+ aioData uint64 // 0: opaque user data echoed back in the completion
+ aioKeyRWFlags uint64 // 8: aio_key (lo32) + aio_rw_flags (hi32)
+ aioLioOpcode uint16 // 16: IOCB_CMD_* opcode
+ aioReqprio int16 // 18: request priority
+ aioFildes uint32 // 20: target file descriptor
+ aioBuf uint64 // 24: userspace data buffer pointer
+ aioNbytes uint64 // 32: byte count
+ aioOffset int64 // 40: file offset
+ aioReserved2 uint64 // 48: reserved, must be zero
+ aioFlags uint32 // 56: IOCB_FLAG_* flags
+ aioResfd uint32 // 60: eventfd for completion notification
+}
+
// aioSetup exercises io_setup(2): it creates an AIO context (writing the
// context id into a userspace pointer) and then tears it down with
// io_destroy(2). io_setup needs no special privileges, so this runs end-to-end
@@ -54,6 +80,68 @@ func aioSetupEinval() error {
return nil
}
+// aioSubmit exercises io_submit(2) end-to-end: it sets up an AIO context,
+// submits a single positional-write iocb against a temp file, then tears the
+// context down. This drives a real io_submit tracepoint so the integration
+// harness can validate that ior records enter_io_submit/exit_io_submit for the
+// AIO family. Note io_submit returns the COUNT of iocbs submitted (here 1), NOT
+// a byte count, which is why the tracer must classify its return UNCLASSIFIED.
+func aioSubmit() error {
+ dir, cleanup, err := makeTempDir("aio-submit")
+ if err != nil {
+ return err
+ }
+ defer cleanup()
+
+ path := filepath.Join(dir, "aio-target")
+ f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0o600)
+ if err != nil {
+ return fmt.Errorf("open aio target: %w", err)
+ }
+ defer f.Close()
+
+ ctx, err := ioSetupContext(aioMaxEvents)
+ if err != nil {
+ return err
+ }
+ defer ioDestroyContext(ctx)
+
+ return ioSubmitWrite(ctx, int(f.Fd()))
+}
+
+// ioSubmitWrite submits one IOCB_CMD_PWRITE iocb against fd via io_submit(2).
+// io_submit takes (ctx_id, nr, iocbpp): an aio_context_t handle (NOT an fd), a
+// count, and a userspace array of iocb pointers. On success it returns the
+// number of iocbs accepted (1 here).
+func ioSubmitWrite(ctx uint64, fd int) error {
+ buf := []byte("ior-aio-submit\n")
+ cb := iocb{
+ aioLioOpcode: iocbCmdPwrite,
+ aioFildes: uint32(fd),
+ aioBuf: uint64(uintptr(unsafe.Pointer(&buf[0]))),
+ aioNbytes: uint64(len(buf)),
+ aioOffset: 0,
+ }
+ cbp := &cb
+ cbs := []*iocb{cbp}
+
+ ret, _, errno := syscall.Syscall(
+ sysIoSubmit,
+ uintptr(ctx),
+ uintptr(len(cbs)),
+ uintptr(unsafe.Pointer(&cbs[0])),
+ )
+ runtime.KeepAlive(buf)
+ runtime.KeepAlive(cbp)
+ if errno != 0 {
+ return fmt.Errorf("io_submit: %w", errno)
+ }
+ if ret != uintptr(len(cbs)) {
+ return fmt.Errorf("io_submit submitted %d iocbs, want %d", ret, len(cbs))
+ }
+ return nil
+}
+
// ioSetupContext calls io_setup(2) and returns the opaque aio_context_t id.
func ioSetupContext(nrEvents uint32) (uint64, error) {
var ctx uint64
diff --git a/cmd/ioworkload/scenarios.go b/cmd/ioworkload/scenarios.go
index 2c4737a..7f42106 100644
--- a/cmd/ioworkload/scenarios.go
+++ b/cmd/ioworkload/scenarios.go
@@ -125,6 +125,7 @@ var scenarios = map[string]func() error{
"iouring-register-ebadf": iouringRegisterEbadf,
"aio-setup": aioSetup,
"aio-setup-einval": aioSetupEinval,
+ "aio-submit": aioSubmit,
}
func makeTempDir(prefix string) (string, func(), error) {