diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-31 10:00:22 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-31 10:00:22 +0300 |
| commit | c6573c61435a23194087558378be3efc1a47fdc4 (patch) | |
| tree | c88e83dc0a0b29223236ee2042f13bfbb27d1106 /cmd | |
| parent | e8948e9f52235cbbbd8af098bc624bc20e173e6c (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.go | 88 | ||||
| -rw-r--r-- | cmd/ioworkload/scenarios.go | 1 |
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) { |
