All of lore.kernel.org
 help / color / mirror / Atom feed
From: Hanna Czenczek <hreitz@redhat.com>
To: qemu-block@nongnu.org
Cc: qemu-devel@nongnu.org, Hanna Czenczek <hreitz@redhat.com>,
	Kevin Wolf <kwolf@redhat.com>, John Snow <jsnow@redhat.com>,
	"Denis V . Lunev" <den@openvz.org>,
	Eric Blake <eblake@redhat.com>,
	Markus Armbruster <armbru@redhat.com>,
	Stefan Hajnoczi <stefanha@redhat.com>,
	Paolo Bonzini <pbonzini@redhat.com>
Subject: [PATCH v2 10/12] block: Per-request delay tracker
Date: Mon, 19 Oct 2026 20:20:26 +0200	[thread overview]
Message-ID: <20261019182028.45305-11-hreitz@redhat.com> (raw)
In-Reply-To: <20261019182028.45305-1-hreitz@redhat.com>

Register I/O requests to BlockBackends with a per-BB timer running in
the background (in the main context) that goes through the list of
requests to find any that surpass a user-defined[1] threshold and then
emit a QMP event calling this out, or stopping the VM altogether so the
guest can be shielded of absolutely excessive delay.

One example for where stopping may be useful: Windows will cancel
requests after a set timeout, but virtio-blk does not support cancelling
requests (by design, I understand).  So it will cancel again, and again,
and then reset the whole driver (which however won’t cancel the request
either), and eventually declare the driver faulty and show a BSOD.  The
only ways around this would be to implement request cancellation (which
I think is not generally possible), or, well, pause the VM in case of
absolutely excessive I/O request delay, and resume it when the request
is done (BLOCK_IO_DONE event), so that Windows will not see that massive
delay.

[1] Not yet user-defined in this commit, but a future commit will allow
    users to specify the threshold when to take action, on the guest
    device.

Signed-off-by: Hanna Czenczek <hreitz@redhat.com>
---
 include/block/block-backend-delay.h         | 112 +++++++
 include/system/block-backend-global-state.h |  11 +
 block/block-backend-delay.c                 | 326 ++++++++++++++++++++
 block/block-backend.c                       |  44 ++-
 block/meson.build                           |   1 +
 5 files changed, 492 insertions(+), 2 deletions(-)
 create mode 100644 include/block/block-backend-delay.h
 create mode 100644 block/block-backend-delay.c

diff --git a/include/block/block-backend-delay.h b/include/block/block-backend-delay.h
new file mode 100644
index 00000000000..9af2e6de925
--- /dev/null
+++ b/include/block/block-backend-delay.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2026 Hanna Czenczek <hreitz@redhat.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef BLOCK_BACKEND_DELAY_H
+#define BLOCK_BACKEND_DELAY_H
+
+#include "qemu/thread.h"
+#include "qemu/timer.h"
+#include "qapi/qapi-types-block.h"
+
+/* Enumeration of I/O delay threshold types */
+enum BlockDelayThreshold {
+    /* Latency threshold at which to report a delay */
+    BLOCK_DELAY_REPORT,
+    /* Latency threshold at which to auto-stop the VM */
+    BLOCK_DELAY_STOP,
+
+    /* Number of different thresholds */
+    BLOCK_DELAY__MAX,
+};
+
+typedef struct BlockDelayTracker {
+    /* Timer to regularly check requests for delay */
+    QEMUTimer *timer;
+
+    /* Which clock to use */
+    QEMUClockType clock_type;
+
+    /*
+     * Protects access to all subsequent fields in this struct. Do not take any
+     * other lock while this lock is taken.
+     */
+    QemuMutex lock;
+
+    /* Thresholds for generating delay report events or stopping the VM */
+    uint64_t threshold_ns[BLOCK_DELAY__MAX];
+
+    /* List of active requests; new requests appended at the end */
+    QTAILQ_HEAD(, BlockDelayTrackerRequest) requests;
+} BlockDelayTracker;
+
+typedef struct BlockDelayTrackerRequest {
+    /* offset < 0 means no valid offset */
+    int64_t offset;
+    /* Request length, or 0 if no valid length */
+    uint64_t bytes;
+    /* When the request was started */
+    uint64_t start_time_ns;
+    /* Operation type */
+    IoAccountingOperation op_type;
+    /*
+     * Whether any delay threshold has been exceeded.
+     * If so, we need to emit BLOCK_IO_DONE when the request is done.
+     */
+    bool threshold_exceeded[BLOCK_DELAY__MAX];
+    /*
+     * Set the first time any threshold is exceeded, to cache the device name
+     * for BLOCK_IO_DONE.
+     */
+    char *device_path;
+
+    /* Entry in the list of all running requests for this BB */
+    QTAILQ_ENTRY(BlockDelayTrackerRequest) list;
+} BlockDelayTrackerRequest;
+
+/**
+ * Set up the request latency delay tracker for this BlockBackend.
+ *
+ * The caller must allocate blk->delay_tracker beforehand (because
+ * block-backend-delay.c cannot access BlockBackend fields directly).
+ */
+void blk_delay_tracker_init(BlockBackend *blk);
+
+/**
+ * Stop and destroy the request latency delay tracker for this BlockBackend.
+ */
+void blk_delay_tracker_destroy(BlockDelayTracker *bdt);
+
+/**
+ * Return the latency delay tracker object for the given BlockBackend.
+ *
+ * (Implemented in block-backend.c, where the BlockBackend definition resides.)
+ */
+BlockDelayTracker *blk_delay_tracker(BlockBackend *blk);
+
+/**
+ * Begin tracking an I/O request on @blk of type @op_type over @bytes starting
+ * from @offset.
+ *
+ * @request is initialized by this function.
+ *
+ * Use offset=-1 for operations that have no valid offset (e.g. flush).
+ */
+void blk_track_delay_begin(BlockBackend *blk,
+                           BlockDelayTrackerRequest *request,
+                           int64_t offset,
+                           uint64_t bytes,
+                           IoAccountingOperation op_type);
+/**
+ * Finalize I/O request tracking.
+ *
+ * @ret indicates the operation's status, with 0 for success and -errno for
+ * errors.
+ */
+void blk_track_delay_end(BlockBackend *blk,
+                         BlockDelayTrackerRequest *request,
+                         int ret);
+
+#endif
diff --git a/include/system/block-backend-global-state.h b/include/system/block-backend-global-state.h
index c3849640df9..c04fcff81a7 100644
--- a/include/system/block-backend-global-state.h
+++ b/include/system/block-backend-global-state.h
@@ -102,6 +102,17 @@ void blk_update_root_state(BlockBackend *blk);
 bool blk_get_detect_zeroes_from_root_state(BlockBackend *blk);
 int blk_get_open_flags_from_root_state(BlockBackend *blk);
 
+/**
+ * Set the nanosecond threshold for per-request latency, when to consider it
+ * sufficient of a delay to report via BLOCK_IO_DELAY QMP event.
+ */
+void blk_set_delay_report_threshold(BlockBackend *blk, uint64_t ns);
+/**
+ * Set the nanosecond threshold for per-request latency, when to consider it
+ * sufficient of a delay to stop the VM.
+ */
+void blk_set_delay_stop_threshold(BlockBackend *blk, uint64_t ns);
+
 int blk_save_vmstate(BlockBackend *blk, const uint8_t *buf,
                      int64_t pos, int size);
 int blk_load_vmstate(BlockBackend *blk, uint8_t *buf, int64_t pos, int size);
diff --git a/block/block-backend-delay.c b/block/block-backend-delay.c
new file mode 100644
index 00000000000..07891573861
--- /dev/null
+++ b/block/block-backend-delay.c
@@ -0,0 +1,326 @@
+/*
+ * Keep track of ongoing BlockBackend requests, notify about excessive
+ * latency (i.e. delays), and potentially even pause the VM until such a
+ * request is done.
+ *
+ * Copyright (c) 2026 Hanna Czenczek <hreitz@redhat.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "block/block-backend-delay.h"
+#include "qapi/qapi-events-block.h"
+#include "system/block-backend.h"
+#include "system/qtest.h"
+#include "system/runstate.h"
+
+/**
+ * Information for BLOCK_IO_DELAY/DONE events so that:
+ * - delay_track_timer() can collect all of the events to emit in a list and
+ *   emit them outside of the BlockDelayTracker lock.
+ * - blk_track_delay_end() can move the event emission to the main loop for
+ *   ordering
+ */
+typedef struct DelayEvent DelayEvent;
+struct DelayEvent {
+    char *device_path;
+    IoAccountingOperation op_type;
+    uint64_t duration_ns;
+    union {
+        BlockErrorAction action; /* for BLOCK_IO_DELAY */
+        int ret;                 /* for BLOCK_IO_DONE */
+    };
+    bool has_offset;
+    uint64_t offset;
+    uint64_t bytes;
+    QTAILQ_ENTRY(DelayEvent) next;
+};
+
+static void delay_track_timer(void *opaque);
+static void send_done_event(void *opaque);
+
+void blk_delay_tracker_init(BlockBackend *blk)
+{
+    BlockDelayTracker *bdt = blk_delay_tracker(blk);
+
+    assert(bdt != NULL);
+
+    bdt->clock_type = qtest_enabled() ? QEMU_CLOCK_VIRTUAL
+                                      : QEMU_CLOCK_REALTIME;
+
+    qemu_mutex_init(&bdt->lock);
+    QTAILQ_INIT(&bdt->requests);
+
+    bdt->timer = aio_timer_new(qemu_get_aio_context(), bdt->clock_type,
+                               SCALE_NS, delay_track_timer, blk);
+}
+
+void blk_delay_tracker_destroy(BlockDelayTracker *bdt)
+{
+    timer_free(bdt->timer);
+    qemu_mutex_destroy(&bdt->lock);
+    assert(QTAILQ_EMPTY(&bdt->requests));
+    g_free(bdt);
+}
+
+void blk_set_delay_report_threshold(BlockBackend *blk, uint64_t ns)
+{
+    BlockDelayTracker *bdt = blk_delay_tracker(blk);
+
+    WITH_QEMU_LOCK_GUARD(&bdt->lock) {
+        bdt->threshold_ns[BLOCK_DELAY_REPORT] = ns;
+    }
+
+    /* Wake the timer immediately to adjust to the new threshold */
+    timer_mod(bdt->timer, 0);
+}
+
+void blk_set_delay_stop_threshold(BlockBackend *blk, uint64_t ns)
+{
+    BlockDelayTracker *bdt = blk_delay_tracker(blk);
+
+    WITH_QEMU_LOCK_GUARD(&bdt->lock) {
+        bdt->threshold_ns[BLOCK_DELAY_STOP] = ns;
+    }
+
+    /* Wake the timer immediately to adjust to the new threshold */
+    timer_mod(bdt->timer, 0);
+}
+
+void blk_track_delay_begin(BlockBackend *blk,
+                           BlockDelayTrackerRequest *request,
+                           int64_t offset,
+                           uint64_t bytes,
+                           IoAccountingOperation op_type)
+{
+    BlockDelayTracker *bdt = blk_delay_tracker(blk);
+
+    *request = (BlockDelayTrackerRequest) {
+        .offset = offset,
+        .bytes = bytes,
+        .op_type = op_type,
+    };
+
+    WITH_QEMU_LOCK_GUARD(&bdt->lock) {
+        if (!bdt->threshold_ns[BLOCK_DELAY_REPORT] &&
+            !bdt->threshold_ns[BLOCK_DELAY_STOP]) {
+            /* Must be so by 0-initialization of .list */
+            assert(!QTAILQ_IN_USE(request, list));
+            return;
+        }
+
+        /* Set this under the lock so `bdt->requests` is correctly ordered */
+        request->start_time_ns = qemu_clock_get_ns(bdt->clock_type);
+        QTAILQ_INSERT_TAIL(&bdt->requests, request, list);
+    }
+
+}
+
+void blk_track_delay_end(BlockBackend *blk,
+                         BlockDelayTrackerRequest *request,
+                         int ret)
+{
+    BlockDelayTracker *bdt = blk_delay_tracker(blk);
+    bool need_done_event = false;
+
+    if (!QTAILQ_IN_USE(request, list)) {
+        return;
+    }
+
+    WITH_QEMU_LOCK_GUARD(&bdt->lock) {
+        for (int i = 0; i < BLOCK_DELAY__MAX; i++) {
+            need_done_event |= request->threshold_exceeded[i];
+        }
+        QTAILQ_REMOVE(&bdt->requests, request, list);
+    }
+
+    /* `device_path` is set if and only if any `threshold_exceeded` are set */
+    assert(!!request->device_path == need_done_event);
+
+    if (need_done_event) {
+        DelayEvent *event = g_new(DelayEvent, 1);
+        uint64_t now = qemu_clock_get_ns(bdt->clock_type);
+
+        *event = (DelayEvent) {
+            .device_path = request->device_path,
+            .op_type = request->op_type,
+            .duration_ns = usub64_saturate(now, request->start_time_ns),
+            .ret = ret,
+            .has_offset = request->offset >= 0,
+            .offset = request->offset,
+            .bytes = request->bytes,
+        };
+
+        /*
+         * Emit the event in the main loop, where the timer runs, too, so we
+         * always emit BLOCK_IO_DONE after all corresponding BLOCK_IO_DELAY
+         * events.
+         */
+        aio_bh_schedule_oneshot(qemu_get_aio_context(), send_done_event, event);
+    }
+}
+
+/**
+ * Send a BLOCK_IO_DONE event from the `DelayEvent` given via `opaque`.
+ *
+ * This event, and the `device_path` in it, are freed via `g_free()`.
+ */
+static void send_done_event(void *opaque)
+{
+    DelayEvent *event = opaque;
+
+    qapi_event_send_block_io_done(event->device_path,
+                                  event->op_type,
+                                  event->duration_ns,
+                                  event->has_offset, event->offset,
+                                  event->bytes,
+                                  event->ret < 0);
+    g_free(event->device_path);
+    g_free(event);
+}
+
+/**
+ * Track and periodically check for stalled requests.
+ *
+ * @opaque is the `BlockBackend`.
+ *
+ * Emits events when requests take longer than the BLOCK_DELAY_REPORT
+ * threshold, and stops the VM if one takes longer than the BLOCK_DELAY_STOP
+ * threshold.
+ *
+ * Handles spurious wakes.
+ */
+static void delay_track_timer(void *opaque)
+{
+    BlockBackend *blk = opaque;
+    BlockDelayTracker *bdt = blk_delay_tracker(blk);
+    g_autofree char *dev_path = NULL;
+    bool stop_vm = false;
+    uint64_t now;
+    /* When to wake up next */
+    uint64_t timer_time = INT64_MAX;
+    /* List of events to emit */
+    QTAILQ_HEAD(, DelayEvent) delay_events =
+        QTAILQ_HEAD_INITIALIZER(delay_events);
+
+    WITH_QEMU_LOCK_GUARD(&bdt->lock) {
+        now = qemu_clock_get_ns(bdt->clock_type);
+
+        for (int i = 0; i < BLOCK_DELAY__MAX; i++) {
+            BlockDelayTrackerRequest *request;
+            uint64_t threshold_ns = bdt->threshold_ns[i];
+
+            if (!threshold_ns) {
+                continue;
+            }
+
+            /*
+             * Sleep at most for the duration of this threshold, so that we
+             * will regularly check new requests as they come in.
+             */
+            timer_time = MIN(timer_time, uadd64_saturate(now, threshold_ns));
+
+            QTAILQ_FOREACH(request, &bdt->requests, list) {
+                DelayEvent *evt;
+                char *req_dev_path;
+                uint64_t duration_ns;
+
+                /*
+                 * New requests are entered at the end of the list, so it
+                 * is always sorted by start_time_ns (ascending).  We can
+                 * stop iterating on the first request that is not yet older
+                 * than the delay threshold.
+                 */
+                duration_ns = usub64_saturate(now, request->start_time_ns);
+                if (duration_ns < threshold_ns) {
+                    /*
+                     * Ensure waking when we need to emit a delay event for
+                     * this request, the one closest to the delay threshold.
+                     */
+                    timer_time = MIN(timer_time,
+                                     uadd64_saturate(request->start_time_ns,
+                                                     threshold_ns));
+                    break;
+                }
+
+                /* Only emit a delay event once */
+                if (request->threshold_exceeded[i]) {
+                    continue;
+                }
+
+                /*
+                 * Always use the same device path for a single request, so use
+                 * the cached path if it exists already.  Otherwise, use the
+                 * current device path.
+                 */
+                req_dev_path = request->device_path ?: dev_path;
+                if (!req_dev_path) {
+                    assert(!request->device_path && !dev_path);
+
+                    /*
+                     * Fine to call under `BlockDelayTracker.lock` because this
+                     * will not take a lock itself.
+                     * The alternative would be to either call it before the
+                     * lock (not yet knowing if there will be delayed requests),
+                     * or call it after, at which point `request` may no longer
+                     * be alive, which would disallow putting the result into
+                     * `request`. We want to put it into `request`, though, so
+                     * the `BLOCK_IO_DONE` event will always emit the same path
+                     * as the `BLOCK_IO_DELAY` event(s) before.
+                     * So, in order to call `blk_get_attached_dev_path()` only
+                     * when needed and to make `request->device_path` work as a
+                     * per-request cache, we have to call it under the lock.
+                     */
+                    req_dev_path = dev_path = blk_get_attached_dev_path(blk);
+                }
+
+                evt = g_new(DelayEvent, 1);
+                *evt = (DelayEvent) {
+                    .device_path = g_strdup(req_dev_path),
+                    .op_type = request->op_type,
+                    .duration_ns = duration_ns,
+                    .action = i == BLOCK_DELAY_REPORT
+                        ? BLOCK_ERROR_ACTION_REPORT
+                        : BLOCK_ERROR_ACTION_STOP,
+                    .has_offset = request->offset >= 0,
+                    .offset = request->offset,
+                    .bytes = request->bytes,
+                };
+                QTAILQ_INSERT_TAIL(&delay_events, evt, next);
+
+                /* For BLOCK_IO_DONE */
+                if (!request->device_path) {
+                    request->device_path = g_strdup(req_dev_path);
+                }
+                request->threshold_exceeded[i] = true;
+
+                if (i == BLOCK_DELAY_STOP) {
+                    stop_vm = true;
+                }
+            }
+        }
+    }
+
+    if (stop_vm) {
+        qemu_system_vmstop_request_prepare();
+    }
+    while (!QTAILQ_EMPTY(&delay_events)) {
+        DelayEvent *evt = QTAILQ_FIRST(&delay_events);
+        QTAILQ_REMOVE(&delay_events, evt, next);
+
+        qapi_event_send_block_io_delay(evt->device_path, evt->op_type,
+                                       evt->duration_ns, evt->action,
+                                       evt->has_offset, evt->offset,
+                                       evt->bytes);
+        g_free(evt->device_path);
+        g_free(evt);
+    }
+    if (stop_vm) {
+        qemu_system_vmstop_request(RUN_STATE_IO_HANG);
+    }
+
+    if (timer_time < INT64_MAX) {
+        timer_mod_ns(bdt->timer, timer_time);
+    }
+}
diff --git a/block/block-backend.c b/block/block-backend.c
index 78678986466..badc3986f9e 100644
--- a/block/block-backend.c
+++ b/block/block-backend.c
@@ -12,6 +12,7 @@
 
 #include "qemu/osdep.h"
 #include "system/block-backend.h"
+#include "block/block-backend-delay.h"
 #include "block/block_int.h"
 #include "block/blockjob.h"
 #include "block/coroutines.h"
@@ -93,6 +94,8 @@ struct BlockBackend {
      * Accessed with atomic ops.
      */
     unsigned int in_flight;
+
+    BlockDelayTracker *delay_tracker;
 };
 
 typedef struct BlockBackendAIOCB {
@@ -377,6 +380,9 @@ BlockBackend *blk_new(AioContext *ctx, uint64_t perm, uint64_t shared_perm)
     notifier_list_init(&blk->insert_bs_notifiers);
     QLIST_INIT(&blk->aio_notifiers);
 
+    blk->delay_tracker = g_new0(BlockDelayTracker, 1);
+    blk_delay_tracker_init(blk);
+
     QTAILQ_INSERT_TAIL(&block_backends, blk, link);
     return blk;
 }
@@ -481,6 +487,9 @@ static void blk_delete(BlockBackend *blk)
     assert(!blk->refcnt);
     assert(!blk->name);
     assert(!blk->dev);
+
+    blk_delay_tracker_destroy(blk->delay_tracker);
+
     if (blk->public.throttle_group_member.throttle_state) {
         blk_io_limits_disable(blk);
     }
@@ -764,6 +773,12 @@ BlockDriverState *blk_bs(BlockBackend *blk)
     return blk->root ? blk->root->bs : NULL;
 }
 
+BlockDelayTracker *blk_delay_tracker(BlockBackend *blk)
+{
+    IO_OR_GS_CODE();
+    return blk->delay_tracker;
+}
+
 static BlockBackend * GRAPH_RDLOCK bdrv_first_blk(BlockDriverState *bs)
 {
     BdrvChild *child;
@@ -1338,6 +1353,7 @@ blk_co_do_preadv_part(BlockBackend *blk, int64_t offset, int64_t bytes,
     ThrottleGroupMember *tgm = &blk->public.throttle_group_member;
     int ret;
     BlockDriverState *bs;
+    BlockDelayTrackerRequest request;
     IO_CODE();
 
     blk_wait_while_drained(blk, flags);
@@ -1353,6 +1369,8 @@ blk_co_do_preadv_part(BlockBackend *blk, int64_t offset, int64_t bytes,
     }
 
     bdrv_inc_in_flight(bs);
+    blk_track_delay_begin(blk, &request, offset, bytes,
+                          IO_ACCOUNTING_OPERATION_READ);
 
     /* throttling disk I/O */
     if (qatomic_load_acquire(&tgm->throttle_state)) {
@@ -1361,6 +1379,7 @@ blk_co_do_preadv_part(BlockBackend *blk, int64_t offset, int64_t bytes,
 
     ret = bdrv_co_preadv_part(blk->root, offset, bytes, qiov, qiov_offset,
                               flags);
+    blk_track_delay_end(blk, &request, ret);
     bdrv_dec_in_flight(bs);
     return ret;
 }
@@ -1413,6 +1432,7 @@ blk_co_do_pwritev_part(BlockBackend *blk, int64_t offset, int64_t bytes,
     ThrottleGroupMember *tgm = &blk->public.throttle_group_member;
     int ret;
     BlockDriverState *bs;
+    BlockDelayTrackerRequest request;
     IO_CODE();
 
     blk_wait_while_drained(blk, flags);
@@ -1428,6 +1448,8 @@ blk_co_do_pwritev_part(BlockBackend *blk, int64_t offset, int64_t bytes,
     }
 
     bdrv_inc_in_flight(bs);
+    blk_track_delay_begin(blk, &request, offset, bytes,
+                          IO_ACCOUNTING_OPERATION_WRITE);
     /* throttling disk I/O */
     if (qatomic_load_acquire(&tgm->throttle_state)) {
         throttle_group_co_io_limits_intercept(tgm, bytes, THROTTLE_WRITE);
@@ -1439,6 +1461,7 @@ blk_co_do_pwritev_part(BlockBackend *blk, int64_t offset, int64_t bytes,
 
     ret = bdrv_co_pwritev_part(blk->root, offset, bytes, qiov, qiov_offset,
                                flags);
+    blk_track_delay_end(blk, &request, ret);
     bdrv_dec_in_flight(bs);
     return ret;
 }
@@ -1803,6 +1826,7 @@ static int coroutine_fn
 blk_co_do_pdiscard(BlockBackend *blk, int64_t offset, int64_t bytes,
                    BdrvRequestFlags flags)
 {
+    BlockDelayTrackerRequest request;
     int ret;
     IO_CODE();
 
@@ -1814,7 +1838,12 @@ blk_co_do_pdiscard(BlockBackend *blk, int64_t offset, int64_t bytes,
         return ret;
     }
 
-    return bdrv_co_pdiscard(blk->root, offset, bytes);
+    blk_track_delay_begin(blk, &request, offset, bytes,
+                          IO_ACCOUNTING_OPERATION_UNMAP);
+    ret = bdrv_co_pdiscard(blk->root, offset, bytes);
+    blk_track_delay_end(blk, &request, ret);
+
+    return ret;
 }
 
 static void coroutine_fn blk_aio_pdiscard_entry(void *opaque)
@@ -1851,6 +1880,8 @@ int coroutine_fn blk_co_pdiscard(BlockBackend *blk, int64_t offset,
 /* To be called between exactly one pair of blk_inc/dec_in_flight() */
 static int coroutine_fn blk_co_do_flush(BlockBackend *blk)
 {
+    BlockDelayTrackerRequest request;
+    int ret;
     IO_CODE();
     blk_wait_while_drained(blk, 0);
     GRAPH_RDLOCK_GUARD();
@@ -1859,7 +1890,12 @@ static int coroutine_fn blk_co_do_flush(BlockBackend *blk)
         return -ENOMEDIUM;
     }
 
-    return bdrv_co_flush(blk_bs(blk));
+    blk_track_delay_begin(blk, &request, -1, 0,
+                          IO_ACCOUNTING_OPERATION_FLUSH);
+    ret = bdrv_co_flush(blk_bs(blk));
+    blk_track_delay_end(blk, &request, ret);
+
+    return ret;
 }
 
 static void coroutine_fn blk_aio_flush_entry(void *opaque)
@@ -2072,6 +2108,7 @@ int coroutine_fn blk_co_zone_mgmt(BlockBackend *blk, BlockZoneOp op,
 int coroutine_fn blk_co_zone_append(BlockBackend *blk, int64_t *offset,
         QEMUIOVector *qiov, BdrvRequestFlags flags)
 {
+    BlockDelayTrackerRequest request;
     int ret;
     IO_CODE();
 
@@ -2083,7 +2120,10 @@ int coroutine_fn blk_co_zone_append(BlockBackend *blk, int64_t *offset,
         return -ENOMEDIUM;
     }
 
+    blk_track_delay_begin(blk, &request, *offset, qiov->size,
+                          IO_ACCOUNTING_OPERATION_ZONE_APPEND);
     ret = bdrv_co_zone_append(blk_bs(blk), offset, qiov, flags);
+    blk_track_delay_end(blk, &request, ret);
     blk_dec_in_flight(blk);
     return ret;
 }
diff --git a/block/meson.build b/block/meson.build
index bc419aebf08..f758c82f3c9 100644
--- a/block/meson.build
+++ b/block/meson.build
@@ -8,6 +8,7 @@ block_ss.add(files(
   'blklogwrites.c',
   'blkverify.c',
   'block-backend.c',
+  'block-backend-delay.c',
   'block-copy.c',
   'commit.c',
   'copy-before-write.c',
-- 
2.55.0



  parent reply	other threads:[~2026-10-19 18:22 UTC|newest]

Thread overview: 18+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-19 18:20 [PATCH v2 00/12] block: BLOCK_IO_DELAY event Hanna Czenczek
2026-10-19 18:20 ` [PATCH v2 01/12] qapi/common: Clarify what IoOperationType is for Hanna Czenczek
2026-10-20  5:58   ` Markus Armbruster
2026-10-19 18:20 ` [PATCH v2 02/12] qapi/block: Add IoAccountingOperation enum Hanna Czenczek
2026-10-20  5:59   ` Markus Armbruster
2026-10-19 18:20 ` [PATCH v2 03/12] runstate: Allow IO_ERROR/SUSPENDED transitions Hanna Czenczek
2026-10-19 18:20 ` [PATCH v2 04/12] iotests: PC-only test for I/O errors during suspend Hanna Czenczek
2026-10-19 18:20 ` [PATCH v2 05/12] qapi/run-state: Add io-hang state Hanna Czenczek
2026-10-20  6:02   ` Markus Armbruster
2026-10-19 18:20 ` [PATCH v2 06/12] qapi/block: Add BLOCK_IO_DELAY event Hanna Czenczek
2026-10-20  6:49   ` Markus Armbruster
2026-10-19 18:20 ` [PATCH v2 07/12] qapi/block: Add BLOCK_IO_DONE event Hanna Czenczek
2026-10-20  6:55   ` Markus Armbruster
2026-10-19 18:20 ` [PATCH v2 08/12] block-backend: Public blk_get_attached_dev_path() Hanna Czenczek
2026-10-19 18:20 ` [PATCH v2 09/12] host-utils: Add u{add,sub}{32,64}_saturate() Hanna Czenczek
2026-10-19 18:20 ` Hanna Czenczek [this message]
2026-10-19 18:20 ` [PATCH v2 11/12] block: Add delay-report-ns and delay-stop-ns props Hanna Czenczek
2026-10-19 18:20 ` [PATCH v2 12/12] iotests: Add io-delay-reporting test Hanna Czenczek

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261019182028.45305-11-hreitz@redhat.com \
    --to=hreitz@redhat.com \
    --cc=armbru@redhat.com \
    --cc=den@openvz.org \
    --cc=eblake@redhat.com \
    --cc=jsnow@redhat.com \
    --cc=kwolf@redhat.com \
    --cc=pbonzini@redhat.com \
    --cc=qemu-block@nongnu.org \
    --cc=qemu-devel@nongnu.org \
    --cc=stefanha@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.