218e99
From 766239ec3a7be7b8bf250a677e478773ac1b96dd Mon Sep 17 00:00:00 2001
218e99
From: Kevin Wolf <kwolf@redhat.com>
218e99
Date: Mon, 9 Sep 2013 14:28:02 +0200
218e99
Subject: [PATCH 11/38] qcow2: Batch discards
218e99
218e99
RH-Author: Kevin Wolf <kwolf@redhat.com>
218e99
Message-id: <1378736903-18489-12-git-send-email-kwolf@redhat.com>
218e99
Patchwork-id: 54199
218e99
O-Subject: [RHEL-7.0 qemu-kvm PATCH 11/32] qcow2: Batch discards
218e99
Bugzilla: 1005818
218e99
RH-Acked-by: Fam Zheng <famz@redhat.com>
218e99
RH-Acked-by: Max Reitz <mreitz@redhat.com>
218e99
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
218e99
218e99
Bugzilla: 1005818
218e99
218e99
This optimises the discard operation for freed clusters by batching
218e99
discard requests (both snapshot deletion and bdrv_discard end up
218e99
updating the refcounts cluster by cluster).
218e99
218e99
Note that we don't discard asynchronously, but keep s->lock held. This
218e99
is to avoid that a freed cluster is reallocated and written to while the
218e99
discard is still in flight.
218e99
218e99
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
218e99
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
218e99
(cherry picked from commit 0b919fae31f4f80f78ebeab8388dc3a03cc5e4ed)
218e99
218e99
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
218e99
---
218e99
 block/qcow2-cluster.c  | 22 +++++++++++---
218e99
 block/qcow2-refcount.c | 82 ++++++++++++++++++++++++++++++++++++++++++++++++--
218e99
 block/qcow2.c          |  1 +
218e99
 block/qcow2.h          | 11 +++++++
218e99
 4 files changed, 109 insertions(+), 7 deletions(-)
218e99
218e99
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
218e99
---
218e99
 block/qcow2-cluster.c  |   22 ++++++++++--
218e99
 block/qcow2-refcount.c |   82 ++++++++++++++++++++++++++++++++++++++++++++++--
218e99
 block/qcow2.c          |    1 +
218e99
 block/qcow2.h          |   11 ++++++
218e99
 4 files changed, 109 insertions(+), 7 deletions(-)
218e99
218e99
diff --git a/block/qcow2-cluster.c b/block/qcow2-cluster.c
218e99
index 3191d6b..cca76d4 100644
218e99
--- a/block/qcow2-cluster.c
218e99
+++ b/block/qcow2-cluster.c
218e99
@@ -1377,18 +1377,25 @@ int qcow2_discard_clusters(BlockDriverState *bs, uint64_t offset,
218e99
 
218e99
     nb_clusters = size_to_clusters(s, end_offset - offset);
218e99
 
218e99
+    s->cache_discards = true;
218e99
+
218e99
     /* Each L2 table is handled by its own loop iteration */
218e99
     while (nb_clusters > 0) {
218e99
         ret = discard_single_l2(bs, offset, nb_clusters);
218e99
         if (ret < 0) {
218e99
-            return ret;
218e99
+            goto fail;
218e99
         }
218e99
 
218e99
         nb_clusters -= ret;
218e99
         offset += (ret * s->cluster_size);
218e99
     }
218e99
 
218e99
-    return 0;
218e99
+    ret = 0;
218e99
+fail:
218e99
+    s->cache_discards = false;
218e99
+    qcow2_process_discards(bs, ret);
218e99
+
218e99
+    return ret;
218e99
 }
218e99
 
218e99
 /*
218e99
@@ -1450,15 +1457,22 @@ int qcow2_zero_clusters(BlockDriverState *bs, uint64_t offset, int nb_sectors)
218e99
     /* Each L2 table is handled by its own loop iteration */
218e99
     nb_clusters = size_to_clusters(s, nb_sectors << BDRV_SECTOR_BITS);
218e99
 
218e99
+    s->cache_discards = true;
218e99
+
218e99
     while (nb_clusters > 0) {
218e99
         ret = zero_single_l2(bs, offset, nb_clusters);
218e99
         if (ret < 0) {
218e99
-            return ret;
218e99
+            goto fail;
218e99
         }
218e99
 
218e99
         nb_clusters -= ret;
218e99
         offset += (ret * s->cluster_size);
218e99
     }
218e99
 
218e99
-    return 0;
218e99
+    ret = 0;
218e99
+fail:
218e99
+    s->cache_discards = false;
218e99
+    qcow2_process_discards(bs, ret);
218e99
+
218e99
+    return ret;
218e99
 }
218e99
diff --git a/block/qcow2-refcount.c b/block/qcow2-refcount.c
218e99
index 7488988..1244693 100644
218e99
--- a/block/qcow2-refcount.c
218e99
+++ b/block/qcow2-refcount.c
218e99
@@ -420,6 +420,74 @@ fail_block:
218e99
     return ret;
218e99
 }
218e99
 
218e99
+void qcow2_process_discards(BlockDriverState *bs, int ret)
218e99
+{
218e99
+    BDRVQcowState *s = bs->opaque;
218e99
+    Qcow2DiscardRegion *d, *next;
218e99
+
218e99
+    QTAILQ_FOREACH_SAFE(d, &s->discards, next, next) {
218e99
+        QTAILQ_REMOVE(&s->discards, d, next);
218e99
+
218e99
+        /* Discard is optional, ignore the return value */
218e99
+        if (ret >= 0) {
218e99
+            bdrv_discard(bs->file,
218e99
+                         d->offset >> BDRV_SECTOR_BITS,
218e99
+                         d->bytes >> BDRV_SECTOR_BITS);
218e99
+        }
218e99
+
218e99
+        g_free(d);
218e99
+    }
218e99
+}
218e99
+
218e99
+static void update_refcount_discard(BlockDriverState *bs,
218e99
+                                    uint64_t offset, uint64_t length)
218e99
+{
218e99
+    BDRVQcowState *s = bs->opaque;
218e99
+    Qcow2DiscardRegion *d, *p, *next;
218e99
+
218e99
+    QTAILQ_FOREACH(d, &s->discards, next) {
218e99
+        uint64_t new_start = MIN(offset, d->offset);
218e99
+        uint64_t new_end = MAX(offset + length, d->offset + d->bytes);
218e99
+
218e99
+        if (new_end - new_start <= length + d->bytes) {
218e99
+            /* There can't be any overlap, areas ending up here have no
218e99
+             * references any more and therefore shouldn't get freed another
218e99
+             * time. */
218e99
+            assert(d->bytes + length == new_end - new_start);
218e99
+            d->offset = new_start;
218e99
+            d->bytes = new_end - new_start;
218e99
+            goto found;
218e99
+        }
218e99
+    }
218e99
+
218e99
+    d = g_malloc(sizeof(*d));
218e99
+    *d = (Qcow2DiscardRegion) {
218e99
+        .bs     = bs,
218e99
+        .offset = offset,
218e99
+        .bytes  = length,
218e99
+    };
218e99
+    QTAILQ_INSERT_TAIL(&s->discards, d, next);
218e99
+
218e99
+found:
218e99
+    /* Merge discard requests if they are adjacent now */
218e99
+    QTAILQ_FOREACH_SAFE(p, &s->discards, next, next) {
218e99
+        if (p == d
218e99
+            || p->offset > d->offset + d->bytes
218e99
+            || d->offset > p->offset + p->bytes)
218e99
+        {
218e99
+            continue;
218e99
+        }
218e99
+
218e99
+        /* Still no overlap possible */
218e99
+        assert(p->offset == d->offset + d->bytes
218e99
+            || d->offset == p->offset + p->bytes);
218e99
+
218e99
+        QTAILQ_REMOVE(&s->discards, p, next);
218e99
+        d->offset = MIN(d->offset, p->offset);
218e99
+        d->bytes += p->bytes;
218e99
+    }
218e99
+}
218e99
+
218e99
 /* XXX: cache several refcount block clusters ? */
218e99
 static int QEMU_WARN_UNUSED_RESULT update_refcount(BlockDriverState *bs,
218e99
     int64_t offset, int64_t length, int addend, enum qcow2_discard_type type)
218e99
@@ -488,15 +556,18 @@ static int QEMU_WARN_UNUSED_RESULT update_refcount(BlockDriverState *bs,
218e99
             s->free_cluster_index = cluster_index;
218e99
         }
218e99
         refcount_block[block_index] = cpu_to_be16(refcount);
218e99
+
218e99
         if (refcount == 0 && s->discard_passthrough[type]) {
218e99
-            /* Try discarding, ignore errors */
218e99
-            /* FIXME Doing this cluster by cluster will be painfully slow */
218e99
-            bdrv_discard(bs->file, cluster_offset, 1);
218e99
+            update_refcount_discard(bs, cluster_offset, s->cluster_size);
218e99
         }
218e99
     }
218e99
 
218e99
     ret = 0;
218e99
 fail:
218e99
+    if (!s->cache_discards) {
218e99
+        qcow2_process_discards(bs, ret);
218e99
+    }
218e99
+
218e99
     /* Write last changed block to disk */
218e99
     if (refcount_block) {
218e99
         int wret;
218e99
@@ -755,6 +826,8 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
218e99
     l1_table = NULL;
218e99
     l1_size2 = l1_size * sizeof(uint64_t);
218e99
 
218e99
+    s->cache_discards = true;
218e99
+
218e99
     /* WARNING: qcow2_snapshot_goto relies on this function not using the
218e99
      * l1_table_offset when it is the current s->l1_table_offset! Be careful
218e99
      * when changing this! */
218e99
@@ -867,6 +940,9 @@ fail:
218e99
         qcow2_cache_put(bs, s->l2_table_cache, (void**) &l2_table);
218e99
     }
218e99
 
218e99
+    s->cache_discards = false;
218e99
+    qcow2_process_discards(bs, ret);
218e99
+
218e99
     /* Update L1 only if it isn't deleted anyway (addend = -1) */
218e99
     if (ret == 0 && addend >= 0 && l1_modified) {
218e99
         for (i = 0; i < l1_size; i++) {
218e99
diff --git a/block/qcow2.c b/block/qcow2.c
218e99
index ef8a2ca..9383990 100644
218e99
--- a/block/qcow2.c
218e99
+++ b/block/qcow2.c
218e99
@@ -486,6 +486,7 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags)
218e99
     }
218e99
 
218e99
     QLIST_INIT(&s->cluster_allocs);
218e99
+    QTAILQ_INIT(&s->discards);
218e99
 
218e99
     /* read qcow2 extensions */
218e99
     if (qcow2_read_extensions(bs, header.header_length, ext_end, NULL)) {
218e99
diff --git a/block/qcow2.h b/block/qcow2.h
218e99
index 6f91b9a..3b2d5cd 100644
218e99
--- a/block/qcow2.h
218e99
+++ b/block/qcow2.h
218e99
@@ -147,6 +147,13 @@ typedef struct Qcow2Feature {
218e99
     char    name[46];
218e99
 } QEMU_PACKED Qcow2Feature;
218e99
 
218e99
+typedef struct Qcow2DiscardRegion {
218e99
+    BlockDriverState *bs;
218e99
+    uint64_t offset;
218e99
+    uint64_t bytes;
218e99
+    QTAILQ_ENTRY(Qcow2DiscardRegion) next;
218e99
+} Qcow2DiscardRegion;
218e99
+
218e99
 typedef struct BDRVQcowState {
218e99
     int cluster_bits;
218e99
     int cluster_size;
218e99
@@ -199,6 +206,8 @@ typedef struct BDRVQcowState {
218e99
     size_t unknown_header_fields_size;
218e99
     void* unknown_header_fields;
218e99
     QLIST_HEAD(, Qcow2UnknownHeaderExtension) unknown_header_ext;
218e99
+    QTAILQ_HEAD (, Qcow2DiscardRegion) discards;
218e99
+    bool cache_discards;
218e99
 } BDRVQcowState;
218e99
 
218e99
 /* XXX: use std qcow open function ? */
218e99
@@ -374,6 +383,8 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
218e99
 int qcow2_check_refcounts(BlockDriverState *bs, BdrvCheckResult *res,
218e99
                           BdrvCheckMode fix);
218e99
 
218e99
+void qcow2_process_discards(BlockDriverState *bs, int ret);
218e99
+
218e99
 /* qcow2-cluster.c functions */
218e99
 int qcow2_grow_l1_table(BlockDriverState *bs, uint64_t min_size,
218e99
                         bool exact_size);
218e99
-- 
218e99
1.7.1
218e99