summaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'cmd')
-rw-r--r--cmd/ioworkload/scenario_aio.go147
-rw-r--r--cmd/ioworkload/scenarios.go2
2 files changed, 140 insertions, 9 deletions
diff --git a/cmd/ioworkload/scenario_aio.go b/cmd/ioworkload/scenario_aio.go
index 61bd1ff..f4cecb1 100644
--- a/cmd/ioworkload/scenario_aio.go
+++ b/cmd/ioworkload/scenario_aio.go
@@ -14,9 +14,11 @@ import (
// distinct from the io_uring_* family. We invoke them raw via Syscall because
// the Go standard library does not wrap them.
const (
- sysIoSetup = 206
- sysIoDestroy = 207
- sysIoSubmit = 209
+ sysIoSetup = 206
+ sysIoDestroy = 207
+ sysIoGetevents = 208
+ sysIoSubmit = 209
+ sysIoCancel = 210
// 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
@@ -29,6 +31,17 @@ const (
iocbCmdPwrite = 1
)
+// ioEvent mirrors struct io_event from <linux/aio_abi.h> on x86_64 (32 bytes).
+// io_getevents(2) fills an array of these from the completion ring; we only
+// need the layout to reap a completion, not to trace it (io_getevents args are
+// opaque to the tracer, KindNull).
+type ioEvent struct {
+ data uint64 // 0: aio_data echoed from the submitting iocb
+ obj uint64 // 8: pointer to the originating iocb
+ res int64 // 16: primary result (bytes transferred, or -errno)
+ res2 int64 // 24: secondary result
+}
+
// 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),
@@ -87,7 +100,58 @@ func aioSetupEinval() error {
// 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")
+ return withAioTarget("aio-submit", func(ctx uint64, fd int) error {
+ _, err := ioSubmitWrite(ctx, fd)
+ return err
+ })
+}
+
+// aioGetevents exercises io_getevents(2) end-to-end: it submits one iocb, then
+// reaps its completion with io_getevents(ctx, min_nr, nr, events, timeout)
+// (sys nr 208 on x86_64). This drives a real io_getevents tracepoint so the
+// integration harness can validate enter_io_getevents/exit_io_getevents for the
+// AIO family. io_getevents returns the COUNT of events reaped (not a byte
+// count), which is why the tracer classifies its return UNCLASSIFIED.
+func aioGetevents() error {
+ return withAioTarget("aio-getevents", func(ctx uint64, fd int) error {
+ if _, err := ioSubmitWrite(ctx, fd); err != nil {
+ return err
+ }
+ return ioGeteventsReap(ctx)
+ })
+}
+
+// aioCancel exercises io_cancel(2): it submits one iocb and then calls
+// io_cancel(ctx, iocbp, &result) (sys nr 210 on x86_64). io_cancel is
+// non-deterministic — the I/O frequently completes before the cancel runs, so
+// the syscall often returns -EINVAL/-EAGAIN — but the enter_io_cancel
+// tracepoint fires regardless of the return value, which is all the integration
+// harness asserts on. To keep the context valid we still reap any pending
+// completion with io_getevents afterwards before tearing down.
+func aioCancel() error {
+ return withAioTarget("aio-cancel", func(ctx uint64, fd int) error {
+ cbp, err := ioSubmitWrite(ctx, fd)
+ if err != nil {
+ return err
+ }
+ // Best-effort cancel: ignore the (non-deterministic) return value;
+ // only the enter tracepoint matters for coverage.
+ ioCancelRequest(ctx, cbp)
+ // Drain any pending completion non-blockingly so io_destroy has
+ // nothing left in flight. We must NOT block here: if the cancel
+ // succeeded the request produces no completion event, so a blocking
+ // (min_nr=1) reap would hang.
+ ioGeteventsDrain(ctx)
+ return nil
+ })
+}
+
+// withAioTarget sets up the common AIO scaffolding shared by the submit-based
+// scenarios: a temp dir, a writable target file, and an AIO context. It invokes
+// fn with the context id and the target fd, then tears the context and temp dir
+// down. Factoring this out keeps the individual scenarios short.
+func withAioTarget(label string, fn func(ctx uint64, fd int) error) error {
+ dir, cleanup, err := makeTempDir(label)
if err != nil {
return err
}
@@ -106,14 +170,15 @@ func aioSubmit() error {
}
defer ioDestroyContext(ctx)
- return ioSubmitWrite(ctx, int(f.Fd()))
+ return fn(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 {
+// number of iocbs accepted (1 here) and the submitted iocb pointer, which
+// io_cancel(2) needs to identify the request to cancel.
+func ioSubmitWrite(ctx uint64, fd int) (*iocb, error) {
buf := []byte("ior-aio-submit\n")
cb := iocb{
aioLioOpcode: iocbCmdPwrite,
@@ -134,14 +199,78 @@ func ioSubmitWrite(ctx uint64, fd int) error {
runtime.KeepAlive(buf)
runtime.KeepAlive(cbp)
if errno != 0 {
- return fmt.Errorf("io_submit: %w", errno)
+ return nil, 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, fmt.Errorf("io_submit submitted %d iocbs, want %d", ret, len(cbs))
+ }
+ return cbp, nil
+}
+
+// ioGeteventsReap reaps up to one completion from the AIO context with
+// io_getevents(2). It takes (ctx_id, min_nr, nr, events, timeout): we request
+// at least one event (min_nr=1) and a NULL timeout so the call blocks until the
+// submitted write completes. The aio_context_t handle is NOT an fd and the
+// events/timeout pointers are opaque to the tracer (KindNull enter); the return
+// is a COUNT of events reaped (UNCLASSIFIED), not a byte count.
+func ioGeteventsReap(ctx uint64) error {
+ var events [1]ioEvent
+ ret, _, errno := syscall.Syscall6(
+ sysIoGetevents,
+ uintptr(ctx),
+ 1, // min_nr: block until at least one completion is ready
+ uintptr(len(events)),
+ uintptr(unsafe.Pointer(&events[0])),
+ 0, // timeout: NULL -> wait indefinitely
+ 0,
+ )
+ runtime.KeepAlive(events)
+ if errno != 0 {
+ return fmt.Errorf("io_getevents: %w", errno)
+ }
+ if ret < 1 {
+ return fmt.Errorf("io_getevents reaped %d events, want >= 1", ret)
}
return nil
}
+// ioGeteventsDrain reaps any already-completed events non-blockingly
+// (min_nr=0, NULL timeout) and discards them. It is used by the cancel scenario
+// to clear the completion ring without risking a hang when the cancel succeeded
+// and left no completion behind. Errors are ignored: this is best-effort
+// cleanup, not a tracepoint-bearing assertion path.
+func ioGeteventsDrain(ctx uint64) {
+ var events [1]ioEvent
+ _, _, _ = syscall.Syscall6(
+ sysIoGetevents,
+ uintptr(ctx),
+ 0, // min_nr=0: return immediately even if nothing is ready
+ uintptr(len(events)),
+ uintptr(unsafe.Pointer(&events[0])),
+ 0, // timeout: NULL
+ 0,
+ )
+ runtime.KeepAlive(events)
+}
+
+// ioCancelRequest attempts to cancel the in-flight iocb via io_cancel(2),
+// which takes (ctx_id, iocb, result). The result io_event receives the
+// completion data on a successful cancel. The return value is intentionally
+// ignored by callers: io_cancel races the I/O completion and commonly fails
+// with -EINVAL/-EAGAIN, but the enter_io_cancel tracepoint fires regardless,
+// which is the only thing the integration harness asserts on.
+func ioCancelRequest(ctx uint64, cbp *iocb) {
+ var result ioEvent
+ _, _, _ = syscall.Syscall(
+ sysIoCancel,
+ uintptr(ctx),
+ uintptr(unsafe.Pointer(cbp)),
+ uintptr(unsafe.Pointer(&result)),
+ )
+ runtime.KeepAlive(cbp)
+ runtime.KeepAlive(result)
+}
+
// 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 811127a..50f84e1 100644
--- a/cmd/ioworkload/scenarios.go
+++ b/cmd/ioworkload/scenarios.go
@@ -156,6 +156,8 @@ var scenarios = map[string]func() error{
"aio-setup": aioSetup,
"aio-setup-einval": aioSetupEinval,
"aio-submit": aioSubmit,
+ "aio-getevents": aioGetevents,
+ "aio-cancel": aioCancel,
"signals-basic": signalsBasic,
"misc-basic": miscBasic,
"sched-basic": schedBasic,