All of lore.kernel.org
 help / color / mirror / Atom feed
* [Qemu-devel] [PATCH 0/7] add throttle block driver filter
@ 2017-07-14  9:45 Manos Pitsidianakis
  2017-07-14  9:45 ` [Qemu-devel] [PATCH 1/7] block: move ThrottleGroup membership to ThrottleGroupMember Manos Pitsidianakis
                   ` (6 more replies)
  0 siblings, 7 replies; 17+ messages in thread
From: Manos Pitsidianakis @ 2017-07-14  9:45 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Stefan Hajnoczi, Alberto Garcia, qemu-block

This series adds a throttle block driver filter. Currently throttling is done
at the BlockBackend level. Using block driver interfaces we can move the
throttling to any point in the BDS graph using a throttle node which uses the
existing throttling code. This allows for potentially more complex
configurations (throttling at any point in the graph, chained filters)

There's also the question of how filter drivers affect the rest of the block
layer, especially live block jobs. This won't be tackled in this series though.

Manos Pitsidianakis (7):
  block: move ThrottleGroup membership to ThrottleGroupMember
  block: add aio_context field in ThrottleGroupMember
  block: tidy ThrottleGroupMember initializations
  block: convert ThrottleGroup to object with QOM
  block: add throttle block filter driver
  block: add BlockDevOptionsThrottle to QAPI
  block: add throttle block filter driver interface tests

 block/Makefile.objs             |   1 +
 block/block-backend.c           |  61 +--
 block/qapi.c                    |   8 +-
 block/throttle-groups.c         | 820 +++++++++++++++++++++++++++++++---------
 block/throttle.c                | 345 +++++++++++++++++
 blockdev.c                      |   4 +-
 include/block/throttle-groups.h |  47 ++-
 include/qemu/throttle-options.h |  60 ++-
 include/qemu/throttle.h         |   3 +
 include/sysemu/block-backend.h  |  20 +-
 qapi/block-core.json            |  65 +++-
 tests/qemu-iotests/184          | 237 ++++++++++++
 tests/qemu-iotests/184.out      | 319 ++++++++++++++++
 tests/qemu-iotests/group        |   1 +
 tests/test-throttle.c           | 111 +++---
 util/throttle.c                 | 121 ++++++
 16 files changed, 1913 insertions(+), 310 deletions(-)
 create mode 100644 block/throttle.c
 create mode 100755 tests/qemu-iotests/184
 create mode 100644 tests/qemu-iotests/184.out

-- 
2.11.0

^ permalink raw reply	[flat|nested] 17+ messages in thread

* [Qemu-devel] [PATCH 1/7] block: move ThrottleGroup membership to ThrottleGroupMember
  2017-07-14  9:45 [Qemu-devel] [PATCH 0/7] add throttle block driver filter Manos Pitsidianakis
@ 2017-07-14  9:45 ` Manos Pitsidianakis
  2017-07-24 14:36   ` Stefan Hajnoczi
  2017-07-14  9:45 ` [Qemu-devel] [PATCH 2/7] block: add aio_context field in ThrottleGroupMember Manos Pitsidianakis
                   ` (5 subsequent siblings)
  6 siblings, 1 reply; 17+ messages in thread
From: Manos Pitsidianakis @ 2017-07-14  9:45 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Stefan Hajnoczi, Alberto Garcia, qemu-block

This commit eliminates the 1:1 relationship between BlockBackend and
throttle group state.  Users will be able to create multiple throttle
nodes, each with its own throttle group state, in the future.  The
throttle group state cannot be per-BlockBackend anymore, it must be
per-throttle node. This is done by gathering ThrottleGroup membership
details from BlockBackendPublic into ThrottleGroupMember and refactoring
existing code to use the structure.

Signed-off-by: Manos Pitsidianakis <el13635@mail.ntua.gr>
---

Notes:
    Dropped R-b because I changed some of the comments (removed all BlockBackend
        references in block/throttle-groups.c comments)

 block/block-backend.c           |  66 +++++----
 block/qapi.c                    |   8 +-
 block/throttle-groups.c         | 288 ++++++++++++++++++++--------------------
 blockdev.c                      |   4 +-
 include/block/throttle-groups.h |  39 +++++-
 include/sysemu/block-backend.h  |  20 +--
 tests/test-throttle.c           |  53 ++++----
 7 files changed, 252 insertions(+), 226 deletions(-)

diff --git a/block/block-backend.c b/block/block-backend.c
index fe3542b3f8..30a60c0e7f 100644
--- a/block/block-backend.c
+++ b/block/block-backend.c
@@ -216,9 +216,9 @@ BlockBackend *blk_new(uint64_t perm, uint64_t shared_perm)
     blk->shared_perm = shared_perm;
     blk_set_enable_write_cache(blk, true);
 
-    qemu_co_mutex_init(&blk->public.throttled_reqs_lock);
-    qemu_co_queue_init(&blk->public.throttled_reqs[0]);
-    qemu_co_queue_init(&blk->public.throttled_reqs[1]);
+    qemu_co_mutex_init(&blk->public.throttle_group_member.throttled_reqs_lock);
+    qemu_co_queue_init(&blk->public.throttle_group_member.throttled_reqs[0]);
+    qemu_co_queue_init(&blk->public.throttle_group_member.throttled_reqs[1]);
     block_acct_init(&blk->stats);
 
     notifier_list_init(&blk->remove_bs_notifiers);
@@ -286,7 +286,7 @@ static void blk_delete(BlockBackend *blk)
     assert(!blk->refcnt);
     assert(!blk->name);
     assert(!blk->dev);
-    if (blk->public.throttle_state) {
+    if (blk->public.throttle_group_member.throttle_state) {
         blk_io_limits_disable(blk);
     }
     if (blk->root) {
@@ -597,9 +597,12 @@ BlockBackend *blk_by_public(BlockBackendPublic *public)
  */
 void blk_remove_bs(BlockBackend *blk)
 {
+    ThrottleTimers *tt;
+
     notifier_list_notify(&blk->remove_bs_notifiers, blk);
-    if (blk->public.throttle_state) {
-        throttle_timers_detach_aio_context(&blk->public.throttle_timers);
+    if (blk->public.throttle_group_member.throttle_state) {
+        tt = &blk->public.throttle_group_member.throttle_timers;
+        throttle_timers_detach_aio_context(tt);
     }
 
     blk_update_root_state(blk);
@@ -621,9 +624,10 @@ int blk_insert_bs(BlockBackend *blk, BlockDriverState *bs, Error **errp)
     bdrv_ref(bs);
 
     notifier_list_notify(&blk->insert_bs_notifiers, blk);
-    if (blk->public.throttle_state) {
+    if (blk->public.throttle_group_member.throttle_state) {
         throttle_timers_attach_aio_context(
-            &blk->public.throttle_timers, bdrv_get_aio_context(bs));
+            &blk->public.throttle_group_member.throttle_timers,
+            bdrv_get_aio_context(bs));
     }
 
     return 0;
@@ -985,8 +989,9 @@ int coroutine_fn blk_co_preadv(BlockBackend *blk, int64_t offset,
     bdrv_inc_in_flight(bs);
 
     /* throttling disk I/O */
-    if (blk->public.throttle_state) {
-        throttle_group_co_io_limits_intercept(blk, bytes, false);
+    if (blk->public.throttle_group_member.throttle_state) {
+        throttle_group_co_io_limits_intercept(&blk->public.throttle_group_member,
+                bytes, false);
     }
 
     ret = bdrv_co_preadv(blk->root, offset, bytes, qiov, flags);
@@ -1009,10 +1014,10 @@ int coroutine_fn blk_co_pwritev(BlockBackend *blk, int64_t offset,
     }
 
     bdrv_inc_in_flight(bs);
-
     /* throttling disk I/O */
-    if (blk->public.throttle_state) {
-        throttle_group_co_io_limits_intercept(blk, bytes, true);
+    if (blk->public.throttle_group_member.throttle_state) {
+        throttle_group_co_io_limits_intercept(&blk->public.throttle_group_member,
+                bytes, true);
     }
 
     if (!blk->enable_write_cache) {
@@ -1681,15 +1686,17 @@ static AioContext *blk_aiocb_get_aio_context(BlockAIOCB *acb)
 void blk_set_aio_context(BlockBackend *blk, AioContext *new_context)
 {
     BlockDriverState *bs = blk_bs(blk);
+    ThrottleTimers *tt;
 
     if (bs) {
-        if (blk->public.throttle_state) {
-            throttle_timers_detach_aio_context(&blk->public.throttle_timers);
+        if (blk->public.throttle_group_member.throttle_state) {
+            tt = &blk->public.throttle_group_member.throttle_timers;
+            throttle_timers_detach_aio_context(tt);
         }
         bdrv_set_aio_context(bs, new_context);
-        if (blk->public.throttle_state) {
-            throttle_timers_attach_aio_context(&blk->public.throttle_timers,
-                                               new_context);
+        if (blk->public.throttle_group_member.throttle_state) {
+            tt = &blk->public.throttle_group_member.throttle_timers;
+            throttle_timers_attach_aio_context(tt, new_context);
         }
     }
 }
@@ -1908,33 +1915,34 @@ int blk_commit_all(void)
 /* throttling disk I/O limits */
 void blk_set_io_limits(BlockBackend *blk, ThrottleConfig *cfg)
 {
-    throttle_group_config(blk, cfg);
+    throttle_group_config(&blk->public.throttle_group_member, cfg);
 }
 
 void blk_io_limits_disable(BlockBackend *blk)
 {
-    assert(blk->public.throttle_state);
+    assert(blk->public.throttle_group_member.throttle_state);
     bdrv_drained_begin(blk_bs(blk));
-    throttle_group_unregister_blk(blk);
+    throttle_group_unregister_tgm(&blk->public.throttle_group_member);
     bdrv_drained_end(blk_bs(blk));
 }
 
 /* should be called before blk_set_io_limits if a limit is set */
 void blk_io_limits_enable(BlockBackend *blk, const char *group)
 {
-    assert(!blk->public.throttle_state);
-    throttle_group_register_blk(blk, group);
+    assert(!blk->public.throttle_group_member.throttle_state);
+    throttle_group_register_tgm(&blk->public.throttle_group_member, group);
 }
 
 void blk_io_limits_update_group(BlockBackend *blk, const char *group)
 {
     /* this BB is not part of any group */
-    if (!blk->public.throttle_state) {
+    if (!blk->public.throttle_group_member.throttle_state) {
         return;
     }
 
     /* this BB is a part of the same group than the one we want */
-    if (!g_strcmp0(throttle_group_get_name(blk), group)) {
+    if (!g_strcmp0(throttle_group_get_name(&blk->public.throttle_group_member),
+                group)) {
         return;
     }
 
@@ -1956,8 +1964,8 @@ static void blk_root_drained_begin(BdrvChild *child)
     /* Note that blk->root may not be accessible here yet if we are just
      * attaching to a BlockDriverState that is drained. Use child instead. */
 
-    if (atomic_fetch_inc(&blk->public.io_limits_disabled) == 0) {
-        throttle_group_restart_blk(blk);
+    if (atomic_fetch_inc(&blk->public.throttle_group_member.io_limits_disabled) == 0) {
+        throttle_group_restart_tgm(&blk->public.throttle_group_member);
     }
 }
 
@@ -1966,8 +1974,8 @@ static void blk_root_drained_end(BdrvChild *child)
     BlockBackend *blk = child->opaque;
     assert(blk->quiesce_counter);
 
-    assert(blk->public.io_limits_disabled);
-    atomic_dec(&blk->public.io_limits_disabled);
+    assert(blk->public.throttle_group_member.io_limits_disabled);
+    atomic_dec(&blk->public.throttle_group_member.io_limits_disabled);
 
     if (--blk->quiesce_counter == 0) {
         if (blk->dev_ops && blk->dev_ops->drained_end) {
diff --git a/block/qapi.c b/block/qapi.c
index 080eb8f115..2de6c0d89f 100644
--- a/block/qapi.c
+++ b/block/qapi.c
@@ -67,10 +67,11 @@ BlockDeviceInfo *bdrv_block_device_info(BlockBackend *blk,
     info->backing_file_depth = bdrv_get_backing_file_depth(bs);
     info->detect_zeroes = bs->detect_zeroes;
 
-    if (blk && blk_get_public(blk)->throttle_state) {
+    if (blk && blk_get_public(blk)->throttle_group_member.throttle_state) {
         ThrottleConfig cfg;
+        BlockBackendPublic *blkp = blk_get_public(blk);
 
-        throttle_group_get_config(blk, &cfg);
+        throttle_group_get_config(&blkp->throttle_group_member, &cfg);
 
         info->bps     = cfg.buckets[THROTTLE_BPS_TOTAL].avg;
         info->bps_rd  = cfg.buckets[THROTTLE_BPS_READ].avg;
@@ -118,7 +119,8 @@ BlockDeviceInfo *bdrv_block_device_info(BlockBackend *blk,
         info->iops_size = cfg.op_size;
 
         info->has_group = true;
-        info->group = g_strdup(throttle_group_get_name(blk));
+        info->group =
+            g_strdup(throttle_group_get_name(&blkp->throttle_group_member));
     }
 
     info->write_threshold = bdrv_write_threshold_get(bs);
diff --git a/block/throttle-groups.c b/block/throttle-groups.c
index 890bfded3f..c8ed16ddf8 100644
--- a/block/throttle-groups.c
+++ b/block/throttle-groups.c
@@ -30,7 +30,7 @@
 #include "sysemu/qtest.h"
 
 /* The ThrottleGroup structure (with its ThrottleState) is shared
- * among different BlockBackends and it's independent from
+ * among different ThrottleGroupMembers and it's independent from
  * AioContext, so in order to use it from different threads it needs
  * its own locking.
  *
@@ -40,26 +40,26 @@
  * The whole ThrottleGroup structure is private and invisible to
  * outside users, that only use it through its ThrottleState.
  *
- * In addition to the ThrottleGroup structure, BlockBackendPublic has
+ * In addition to the ThrottleGroup structure, ThrottleGroupMember has
  * fields that need to be accessed by other members of the group and
  * therefore also need to be protected by this lock. Once a
- * BlockBackend is registered in a group those fields can be accessed
+ * ThrottleGroupMember is registered in a group those fields can be accessed
  * by other threads any time.
  *
  * Again, all this is handled internally and is mostly transparent to
  * the outside. The 'throttle_timers' field however has an additional
  * constraint because it may be temporarily invalid (see for example
  * blk_set_aio_context()). Therefore in this file a thread will
- * access some other BlockBackend's timers only after verifying that
- * that BlockBackend has throttled requests in the queue.
+ * access some other ThrottleGroupMember's timers only after verifying that
+ * that ThrottleGroupMember has throttled requests in the queue.
  */
 typedef struct ThrottleGroup {
     char *name; /* This is constant during the lifetime of the group */
 
     QemuMutex lock; /* This lock protects the following four fields */
     ThrottleState ts;
-    QLIST_HEAD(, BlockBackendPublic) head;
-    BlockBackend *tokens[2];
+    QLIST_HEAD(, ThrottleGroupMember) head;
+    ThrottleGroupMember *tokens[2];
     bool any_timer_armed[2];
     QEMUClockType clock_type;
 
@@ -140,114 +140,112 @@ void throttle_group_unref(ThrottleState *ts)
     qemu_mutex_unlock(&throttle_groups_lock);
 }
 
-/* Get the name from a BlockBackend's ThrottleGroup. The name (and the pointer)
+/* Get the name from a ThrottleGroupMember's group. The name (and the pointer)
  * is guaranteed to remain constant during the lifetime of the group.
  *
- * @blk:  a BlockBackend that is member of a throttling group
+ * @tgm:  a ThrottleGroupMember
  * @ret:  the name of the group.
  */
-const char *throttle_group_get_name(BlockBackend *blk)
+const char *throttle_group_get_name(ThrottleGroupMember *tgm)
 {
-    BlockBackendPublic *blkp = blk_get_public(blk);
-    ThrottleGroup *tg = container_of(blkp->throttle_state, ThrottleGroup, ts);
+    ThrottleGroup *tg = container_of(tgm->throttle_state, ThrottleGroup, ts);
     return tg->name;
 }
 
-/* Return the next BlockBackend in the round-robin sequence, simulating a
- * circular list.
+/* Return the next ThrottleGroupMember in the round-robin sequence, simulating
+ * a circular list.
  *
  * This assumes that tg->lock is held.
  *
- * @blk: the current BlockBackend
- * @ret: the next BlockBackend in the sequence
+ * @tgm: the current ThrottleGroupMember
+ * @ret: the next ThrottleGroupMember in the sequence
  */
-static BlockBackend *throttle_group_next_blk(BlockBackend *blk)
+static ThrottleGroupMember *throttle_group_next_tgm(ThrottleGroupMember *tgm)
 {
-    BlockBackendPublic *blkp = blk_get_public(blk);
-    ThrottleState *ts = blkp->throttle_state;
+    ThrottleState *ts = tgm->throttle_state;
     ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts);
-    BlockBackendPublic *next = QLIST_NEXT(blkp, round_robin);
+    ThrottleGroupMember *next = QLIST_NEXT(tgm, round_robin);
 
     if (!next) {
         next = QLIST_FIRST(&tg->head);
     }
 
-    return blk_by_public(next);
+    return next;
 }
 
 /*
- * Return whether a BlockBackend has pending requests.
+ * Return whether a ThrottleGroupMember has pending requests.
  *
  * This assumes that tg->lock is held.
  *
- * @blk: the BlockBackend
- * @is_write:  the type of operation (read/write)
- * @ret:       whether the BlockBackend has pending requests.
+ * @tgm:        the ThrottleGroupMember
+ * @is_write:   the type of operation (read/write)
+ * @ret:        whether the ThrottleGroupMember has pending requests.
  */
-static inline bool blk_has_pending_reqs(BlockBackend *blk,
+static inline bool tgm_has_pending_reqs(ThrottleGroupMember *tgm,
                                         bool is_write)
 {
-    const BlockBackendPublic *blkp = blk_get_public(blk);
-    return blkp->pending_reqs[is_write];
+    return tgm->pending_reqs[is_write];
 }
 
-/* Return the next BlockBackend in the round-robin sequence with pending I/O
- * requests.
+/* Return the next ThrottleGroupMember in the round-robin sequence with pending
+ * I/O requests.
  *
  * This assumes that tg->lock is held.
  *
- * @blk:       the current BlockBackend
+ * @tgm:       the current ThrottleGroupMember
  * @is_write:  the type of operation (read/write)
- * @ret:       the next BlockBackend with pending requests, or blk if there is
- *             none.
+ * @ret:       the next ThrottleGroupMember with pending requests, or tgm if
+ *             there is none.
  */
-static BlockBackend *next_throttle_token(BlockBackend *blk, bool is_write)
+static ThrottleGroupMember *next_throttle_token(ThrottleGroupMember *tgm,
+                                                bool is_write)
 {
-    BlockBackendPublic *blkp = blk_get_public(blk);
-    ThrottleGroup *tg = container_of(blkp->throttle_state, ThrottleGroup, ts);
-    BlockBackend *token, *start;
+    ThrottleState *ts = tgm->throttle_state;
+    ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts);
+    ThrottleGroupMember *token, *start;
 
     start = token = tg->tokens[is_write];
 
     /* get next bs round in round robin style */
-    token = throttle_group_next_blk(token);
-    while (token != start && !blk_has_pending_reqs(token, is_write)) {
-        token = throttle_group_next_blk(token);
+    token = throttle_group_next_tgm(token);
+    while (token != start && !tgm_has_pending_reqs(token, is_write)) {
+        token = throttle_group_next_tgm(token);
     }
 
     /* If no IO are queued for scheduling on the next round robin token
-     * then decide the token is the current bs because chances are
-     * the current bs get the current request queued.
+     * then decide the token is the current tgm because chances are
+     * the current tgm got the current request queued.
      */
-    if (token == start && !blk_has_pending_reqs(token, is_write)) {
-        token = blk;
+    if (token == start && !tgm_has_pending_reqs(token, is_write)) {
+        token = tgm;
     }
 
-    /* Either we return the original BB, or one with pending requests */
-    assert(token == blk || blk_has_pending_reqs(token, is_write));
+    /* Either we return the original TGM, or one with pending requests */
+    assert(token == tgm || tgm_has_pending_reqs(token, is_write));
 
     return token;
 }
 
-/* Check if the next I/O request for a BlockBackend needs to be throttled or
- * not. If there's no timer set in this group, set one and update the token
- * accordingly.
+/* Check if the next I/O request for a ThrottleGroupMember needs to be
+ * throttled or not. If there's no timer set in this group, set one and update
+ * the token accordingly.
  *
  * This assumes that tg->lock is held.
  *
- * @blk:        the current BlockBackend
+ * @tgm:        the current ThrottleGroupMember
  * @is_write:   the type of operation (read/write)
  * @ret:        whether the I/O request needs to be throttled or not
  */
-static bool throttle_group_schedule_timer(BlockBackend *blk, bool is_write)
+static bool throttle_group_schedule_timer(ThrottleGroupMember *tgm,
+                                          bool is_write)
 {
-    BlockBackendPublic *blkp = blk_get_public(blk);
-    ThrottleState *ts = blkp->throttle_state;
-    ThrottleTimers *tt = &blkp->throttle_timers;
+    ThrottleState *ts = tgm->throttle_state;
     ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts);
+    ThrottleTimers *tt = &tgm->throttle_timers;
     bool must_wait;
 
-    if (atomic_read(&blkp->io_limits_disabled)) {
+    if (atomic_read(&tgm->io_limits_disabled)) {
         return false;
     }
 
@@ -258,30 +256,29 @@ static bool throttle_group_schedule_timer(BlockBackend *blk, bool is_write)
 
     must_wait = throttle_schedule_timer(ts, tt, is_write);
 
-    /* If a timer just got armed, set blk as the current token */
+    /* If a timer just got armed, set tgm as the current token */
     if (must_wait) {
-        tg->tokens[is_write] = blk;
+        tg->tokens[is_write] = tgm;
         tg->any_timer_armed[is_write] = true;
     }
 
     return must_wait;
 }
 
-/* Start the next pending I/O request for a BlockBackend.  Return whether
+/* Start the next pending I/O request for a ThrottleGroupMember. Return whether
  * any request was actually pending.
  *
- * @blk:       the current BlockBackend
+ * @tgm:       the current ThrottleGroupMember
  * @is_write:  the type of operation (read/write)
  */
-static bool coroutine_fn throttle_group_co_restart_queue(BlockBackend *blk,
+static bool coroutine_fn throttle_group_co_restart_queue(ThrottleGroupMember *tgm,
                                                          bool is_write)
 {
-    BlockBackendPublic *blkp = blk_get_public(blk);
     bool ret;
 
-    qemu_co_mutex_lock(&blkp->throttled_reqs_lock);
-    ret = qemu_co_queue_next(&blkp->throttled_reqs[is_write]);
-    qemu_co_mutex_unlock(&blkp->throttled_reqs_lock);
+    qemu_co_mutex_lock(&tgm->throttled_reqs_lock);
+    ret = qemu_co_queue_next(&tgm->throttled_reqs[is_write]);
+    qemu_co_mutex_unlock(&tgm->throttled_reqs_lock);
 
     return ret;
 }
@@ -290,19 +287,19 @@ static bool coroutine_fn throttle_group_co_restart_queue(BlockBackend *blk,
  *
  * This assumes that tg->lock is held.
  *
- * @blk:       the current BlockBackend
+ * @tgm:       the current ThrottleGroupMember
  * @is_write:  the type of operation (read/write)
  */
-static void schedule_next_request(BlockBackend *blk, bool is_write)
+static void schedule_next_request(ThrottleGroupMember *tgm, bool is_write)
 {
-    BlockBackendPublic *blkp = blk_get_public(blk);
-    ThrottleGroup *tg = container_of(blkp->throttle_state, ThrottleGroup, ts);
+    ThrottleState *ts = tgm->throttle_state;
+    ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts);
     bool must_wait;
-    BlockBackend *token;
+    ThrottleGroupMember *token;
 
     /* Check if there's any pending request to schedule next */
-    token = next_throttle_token(blk, is_write);
-    if (!blk_has_pending_reqs(token, is_write)) {
+    token = next_throttle_token(tgm, is_write);
+    if (!tgm_has_pending_reqs(token, is_write)) {
         return;
     }
 
@@ -311,12 +308,12 @@ static void schedule_next_request(BlockBackend *blk, bool is_write)
 
     /* If it doesn't have to wait, queue it for immediate execution */
     if (!must_wait) {
-        /* Give preference to requests from the current blk */
+        /* Give preference to requests from the current tgm */
         if (qemu_in_coroutine() &&
-            throttle_group_co_restart_queue(blk, is_write)) {
-            token = blk;
+            throttle_group_co_restart_queue(tgm, is_write)) {
+            token = tgm;
         } else {
-            ThrottleTimers *tt = &blk_get_public(token)->throttle_timers;
+            ThrottleTimers *tt = &token->throttle_timers;
             int64_t now = qemu_clock_get_ns(tg->clock_type);
             timer_mod(tt->timers[is_write], now);
             tg->any_timer_armed[is_write] = true;
@@ -329,76 +326,77 @@ static void schedule_next_request(BlockBackend *blk, bool is_write)
  * if necessary, and schedule the next request using a round robin
  * algorithm.
  *
- * @blk:       the current BlockBackend
+ * @tgm:       the current ThrottleGroupMember
  * @bytes:     the number of bytes for this I/O
  * @is_write:  the type of operation (read/write)
  */
-void coroutine_fn throttle_group_co_io_limits_intercept(BlockBackend *blk,
+void coroutine_fn throttle_group_co_io_limits_intercept(ThrottleGroupMember *tgm,
                                                         unsigned int bytes,
                                                         bool is_write)
 {
     bool must_wait;
-    BlockBackend *token;
-
-    BlockBackendPublic *blkp = blk_get_public(blk);
-    ThrottleGroup *tg = container_of(blkp->throttle_state, ThrottleGroup, ts);
+    ThrottleGroupMember *token;
+    ThrottleGroup *tg = container_of(tgm->throttle_state, ThrottleGroup, ts);
     qemu_mutex_lock(&tg->lock);
 
     /* First we check if this I/O has to be throttled. */
-    token = next_throttle_token(blk, is_write);
+    token = next_throttle_token(tgm, is_write);
     must_wait = throttle_group_schedule_timer(token, is_write);
 
     /* Wait if there's a timer set or queued requests of this type */
-    if (must_wait || blkp->pending_reqs[is_write]) {
-        blkp->pending_reqs[is_write]++;
+    if (must_wait || tgm->pending_reqs[is_write]) {
+        tgm->pending_reqs[is_write]++;
         qemu_mutex_unlock(&tg->lock);
-        qemu_co_mutex_lock(&blkp->throttled_reqs_lock);
-        qemu_co_queue_wait(&blkp->throttled_reqs[is_write],
-                           &blkp->throttled_reqs_lock);
-        qemu_co_mutex_unlock(&blkp->throttled_reqs_lock);
+        qemu_co_mutex_lock(&tgm->throttled_reqs_lock);
+        qemu_co_queue_wait(&tgm->throttled_reqs[is_write],
+                           &tgm->throttled_reqs_lock);
+        qemu_co_mutex_unlock(&tgm->throttled_reqs_lock);
         qemu_mutex_lock(&tg->lock);
-        blkp->pending_reqs[is_write]--;
+        tgm->pending_reqs[is_write]--;
     }
 
     /* The I/O will be executed, so do the accounting */
-    throttle_account(blkp->throttle_state, is_write, bytes);
+    throttle_account(tgm->throttle_state, is_write, bytes);
 
     /* Schedule the next request */
-    schedule_next_request(blk, is_write);
+    schedule_next_request(tgm, is_write);
 
     qemu_mutex_unlock(&tg->lock);
 }
 
 typedef struct {
-    BlockBackend *blk;
+    ThrottleGroupMember *tgm;
     bool is_write;
 } RestartData;
 
 static void coroutine_fn throttle_group_restart_queue_entry(void *opaque)
 {
     RestartData *data = opaque;
-    BlockBackend *blk = data->blk;
+    ThrottleGroupMember *tgm = data->tgm;
+    ThrottleState *ts = tgm->throttle_state;
+    ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts);
     bool is_write = data->is_write;
-    BlockBackendPublic *blkp = blk_get_public(blk);
-    ThrottleGroup *tg = container_of(blkp->throttle_state, ThrottleGroup, ts);
     bool empty_queue;
 
-    empty_queue = !throttle_group_co_restart_queue(blk, is_write);
+    empty_queue = !throttle_group_co_restart_queue(tgm, is_write);
 
     /* If the request queue was empty then we have to take care of
      * scheduling the next one */
     if (empty_queue) {
         qemu_mutex_lock(&tg->lock);
-        schedule_next_request(blk, is_write);
+        schedule_next_request(tgm, is_write);
         qemu_mutex_unlock(&tg->lock);
     }
 }
 
-static void throttle_group_restart_queue(BlockBackend *blk, bool is_write)
+static void throttle_group_restart_queue(ThrottleGroupMember *tgm, bool is_write)
 {
+    BlockBackendPublic *blkp = container_of(tgm, BlockBackendPublic,
+            throttle_group_member);
+    BlockBackend *blk = blk_by_public(blkp);
     Coroutine *co;
     RestartData rd = {
-        .blk = blk,
+        .tgm = tgm,
         .is_write = is_write
     };
 
@@ -406,13 +404,11 @@ static void throttle_group_restart_queue(BlockBackend *blk, bool is_write)
     aio_co_enter(blk_get_aio_context(blk), co);
 }
 
-void throttle_group_restart_blk(BlockBackend *blk)
+void throttle_group_restart_tgm(ThrottleGroupMember *tgm)
 {
-    BlockBackendPublic *blkp = blk_get_public(blk);
-
-    if (blkp->throttle_state) {
-        throttle_group_restart_queue(blk, 0);
-        throttle_group_restart_queue(blk, 1);
+    if (tgm->throttle_state) {
+        throttle_group_restart_queue(tgm, 0);
+        throttle_group_restart_queue(tgm, 1);
     }
 }
 
@@ -420,32 +416,30 @@ void throttle_group_restart_blk(BlockBackend *blk)
  * to throttle_config(), but guarantees atomicity within the
  * throttling group.
  *
- * @blk: a BlockBackend that is a member of the group
+ * @tgm:    a ThrottleGroupMember that is a member of the group
  * @cfg: the configuration to set
  */
-void throttle_group_config(BlockBackend *blk, ThrottleConfig *cfg)
+void throttle_group_config(ThrottleGroupMember *tgm, ThrottleConfig *cfg)
 {
-    BlockBackendPublic *blkp = blk_get_public(blk);
-    ThrottleState *ts = blkp->throttle_state;
+    ThrottleState *ts = tgm->throttle_state;
     ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts);
     qemu_mutex_lock(&tg->lock);
     throttle_config(ts, tg->clock_type, cfg);
     qemu_mutex_unlock(&tg->lock);
 
-    throttle_group_restart_blk(blk);
+    throttle_group_restart_tgm(tgm);
 }
 
 /* Get the throttle configuration from a particular group. Similar to
  * throttle_get_config(), but guarantees atomicity within the
  * throttling group.
  *
- * @blk: a BlockBackend that is a member of the group
+ * @tgm:    a ThrottleGroupMember that is a member of the group
  * @cfg: the configuration will be written here
  */
-void throttle_group_get_config(BlockBackend *blk, ThrottleConfig *cfg)
+void throttle_group_get_config(ThrottleGroupMember *tgm, ThrottleConfig *cfg)
 {
-    BlockBackendPublic *blkp = blk_get_public(blk);
-    ThrottleState *ts = blkp->throttle_state;
+    ThrottleState *ts = tgm->throttle_state;
     ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts);
     qemu_mutex_lock(&tg->lock);
     throttle_get_config(ts, cfg);
@@ -461,7 +455,8 @@ void throttle_group_get_config(BlockBackend *blk, ThrottleConfig *cfg)
 static void timer_cb(BlockBackend *blk, bool is_write)
 {
     BlockBackendPublic *blkp = blk_get_public(blk);
-    ThrottleState *ts = blkp->throttle_state;
+    ThrottleGroupMember *tgm = &blkp->throttle_group_member;
+    ThrottleState *ts = tgm->throttle_state;
     ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts);
 
     /* The timer has just been fired, so we can update the flag */
@@ -470,7 +465,7 @@ static void timer_cb(BlockBackend *blk, bool is_write)
     qemu_mutex_unlock(&tg->lock);
 
     /* Run the request that was waiting for this timer */
-    throttle_group_restart_queue(blk, is_write);
+    throttle_group_restart_queue(tgm, is_write);
 }
 
 static void read_timer_cb(void *opaque)
@@ -483,32 +478,36 @@ static void write_timer_cb(void *opaque)
     timer_cb(opaque, true);
 }
 
-/* Register a BlockBackend in the throttling group, also initializing its
- * timers and updating its throttle_state pointer to point to it. If a
+/* Register a ThrottleGroupMember from the throttling group, also initializing
+ * its timers and updating its throttle_state pointer to point to it. If a
  * throttling group with that name does not exist yet, it will be created.
  *
- * @blk:       the BlockBackend to insert
+ * @tgm:       the ThrottleGroupMember to insert
  * @groupname: the name of the group
  */
-void throttle_group_register_blk(BlockBackend *blk, const char *groupname)
+void throttle_group_register_tgm(ThrottleGroupMember *tgm,
+                                 const char *groupname)
 {
     int i;
-    BlockBackendPublic *blkp = blk_get_public(blk);
+    BlockBackendPublic *blkp = container_of(tgm, BlockBackendPublic,
+            throttle_group_member);
+    BlockBackend *blk = blk_by_public(blkp);
     ThrottleState *ts = throttle_group_incref(groupname);
     ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts);
-    blkp->throttle_state = ts;
+
+    tgm->throttle_state = ts;
 
     qemu_mutex_lock(&tg->lock);
-    /* If the ThrottleGroup is new set this BlockBackend as the token */
+    /* If the ThrottleGroup is new set this ThrottleGroupMember as the token */
     for (i = 0; i < 2; i++) {
         if (!tg->tokens[i]) {
-            tg->tokens[i] = blk;
+            tg->tokens[i] = tgm;
         }
     }
 
-    QLIST_INSERT_HEAD(&tg->head, blkp, round_robin);
+    QLIST_INSERT_HEAD(&tg->head, tgm, round_robin);
 
-    throttle_timers_init(&blkp->throttle_timers,
+    throttle_timers_init(&tgm->throttle_timers,
                          blk_get_aio_context(blk),
                          tg->clock_type,
                          read_timer_cb,
@@ -518,45 +517,46 @@ void throttle_group_register_blk(BlockBackend *blk, const char *groupname)
     qemu_mutex_unlock(&tg->lock);
 }
 
-/* Unregister a BlockBackend from its group, removing it from the list,
+/* Unregister a ThrottleGroupMember from its group, removing it from the list,
  * destroying the timers and setting the throttle_state pointer to NULL.
  *
- * The BlockBackend must not have pending throttled requests, so the caller has
- * to drain them first.
+ * The ThrottleGroupMember must not have pending throttled requests, so the
+ * caller has to drain them first.
  *
  * The group will be destroyed if it's empty after this operation.
  *
- * @blk: the BlockBackend to remove
+ * @tgm the ThrottleGroupMember to remove
  */
-void throttle_group_unregister_blk(BlockBackend *blk)
+void throttle_group_unregister_tgm(ThrottleGroupMember *tgm)
 {
-    BlockBackendPublic *blkp = blk_get_public(blk);
-    ThrottleGroup *tg = container_of(blkp->throttle_state, ThrottleGroup, ts);
+    ThrottleState *ts = tgm->throttle_state;
+    ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts);
+    ThrottleGroupMember *token;
     int i;
 
-    assert(blkp->pending_reqs[0] == 0 && blkp->pending_reqs[1] == 0);
-    assert(qemu_co_queue_empty(&blkp->throttled_reqs[0]));
-    assert(qemu_co_queue_empty(&blkp->throttled_reqs[1]));
+    assert(tgm->pending_reqs[0] == 0 && tgm->pending_reqs[1] == 0);
+    assert(qemu_co_queue_empty(&tgm->throttled_reqs[0]));
+    assert(qemu_co_queue_empty(&tgm->throttled_reqs[1]));
 
     qemu_mutex_lock(&tg->lock);
     for (i = 0; i < 2; i++) {
-        if (tg->tokens[i] == blk) {
-            BlockBackend *token = throttle_group_next_blk(blk);
-            /* Take care of the case where this is the last blk in the group */
-            if (token == blk) {
+        if (tg->tokens[i] == tgm) {
+            token = throttle_group_next_tgm(tgm);
+            /* Take care of the case where this is the last tgm in the group */
+            if (token == tgm) {
                 token = NULL;
             }
             tg->tokens[i] = token;
         }
     }
 
-    /* remove the current blk from the list */
-    QLIST_REMOVE(blkp, round_robin);
-    throttle_timers_destroy(&blkp->throttle_timers);
+    /* remove the current tgm from the list */
+    QLIST_REMOVE(tgm, round_robin);
+    throttle_timers_destroy(&tgm->throttle_timers);
     qemu_mutex_unlock(&tg->lock);
 
     throttle_group_unref(&tg->ts);
-    blkp->throttle_state = NULL;
+    tgm->throttle_state = NULL;
 }
 
 static void throttle_groups_init(void)
diff --git a/blockdev.c b/blockdev.c
index 9c6dd270b7..c18899e468 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -2689,7 +2689,7 @@ void qmp_block_set_io_throttle(BlockIOThrottle *arg, Error **errp)
     if (throttle_enabled(&cfg)) {
         /* Enable I/O limits if they're not enabled yet, otherwise
          * just update the throttling group. */
-        if (!blk_get_public(blk)->throttle_state) {
+        if (!blk_get_public(blk)->throttle_group_member.throttle_state) {
             blk_io_limits_enable(blk,
                                  arg->has_group ? arg->group :
                                  arg->has_device ? arg->device :
@@ -2699,7 +2699,7 @@ void qmp_block_set_io_throttle(BlockIOThrottle *arg, Error **errp)
         }
         /* Set the new throttling configuration */
         blk_set_io_limits(blk, &cfg);
-    } else if (blk_get_public(blk)->throttle_state) {
+    } else if (blk_get_public(blk)->throttle_group_member.throttle_state) {
         /* If all throttling settings are set to 0, disable I/O limits */
         blk_io_limits_disable(blk);
     }
diff --git a/include/block/throttle-groups.h b/include/block/throttle-groups.h
index d983d34074..1a6bcdae74 100644
--- a/include/block/throttle-groups.h
+++ b/include/block/throttle-groups.h
@@ -28,19 +28,44 @@
 #include "qemu/throttle.h"
 #include "block/block_int.h"
 
-const char *throttle_group_get_name(BlockBackend *blk);
+/* The ThrottleGroupMember structure indicates membership in a ThrottleGroup
+ * and holds related data.
+ */
+
+typedef struct ThrottleGroupMember {
+    /* throttled_reqs_lock protects the CoQueues for throttled requests.  */
+    CoMutex      throttled_reqs_lock;
+    CoQueue      throttled_reqs[2];
+
+    /* Nonzero if the I/O limits are currently being ignored; generally
+     * it is zero.  Accessed with atomic operations.
+     */
+    unsigned int io_limits_disabled;
+
+    /* The following fields are protected by the ThrottleGroup lock.
+     * See the ThrottleGroup documentation for details.
+     * throttle_state tells us if I/O limits are configured. */
+    ThrottleState *throttle_state;
+    ThrottleTimers throttle_timers;
+    unsigned       pending_reqs[2];
+    QLIST_ENTRY(ThrottleGroupMember) round_robin;
+
+} ThrottleGroupMember;
+
+const char *throttle_group_get_name(ThrottleGroupMember *tgm);
 
 ThrottleState *throttle_group_incref(const char *name);
 void throttle_group_unref(ThrottleState *ts);
 
-void throttle_group_config(BlockBackend *blk, ThrottleConfig *cfg);
-void throttle_group_get_config(BlockBackend *blk, ThrottleConfig *cfg);
+void throttle_group_config(ThrottleGroupMember *tgm, ThrottleConfig *cfg);
+void throttle_group_get_config(ThrottleGroupMember *tgm, ThrottleConfig *cfg);
 
-void throttle_group_register_blk(BlockBackend *blk, const char *groupname);
-void throttle_group_unregister_blk(BlockBackend *blk);
-void throttle_group_restart_blk(BlockBackend *blk);
+void throttle_group_register_tgm(ThrottleGroupMember *tgm,
+                                const char *groupname);
+void throttle_group_unregister_tgm(ThrottleGroupMember *tgm);
+void throttle_group_restart_tgm(ThrottleGroupMember *tgm);
 
-void coroutine_fn throttle_group_co_io_limits_intercept(BlockBackend *blk,
+void coroutine_fn throttle_group_co_io_limits_intercept(ThrottleGroupMember *tgm,
                                                         unsigned int bytes,
                                                         bool is_write);
 
diff --git a/include/sysemu/block-backend.h b/include/sysemu/block-backend.h
index d9ea0cdb0f..2e5c14fa29 100644
--- a/include/sysemu/block-backend.h
+++ b/include/sysemu/block-backend.h
@@ -70,24 +70,10 @@ typedef struct BlockDevOps {
 
 /* This struct is embedded in (the private) BlockBackend struct and contains
  * fields that must be public. This is in particular for QLIST_ENTRY() and
- * friends so that BlockBackends can be kept in lists outside block-backend.c */
+ * friends so that BlockBackends can be kept in lists outside block-backend.c
+ * */
 typedef struct BlockBackendPublic {
-    /* throttled_reqs_lock protects the CoQueues for throttled requests.  */
-    CoMutex      throttled_reqs_lock;
-    CoQueue      throttled_reqs[2];
-
-    /* Nonzero if the I/O limits are currently being ignored; generally
-     * it is zero.  Accessed with atomic operations.
-     */
-    unsigned int io_limits_disabled;
-
-    /* The following fields are protected by the ThrottleGroup lock.
-     * See the ThrottleGroup documentation for details.
-     * throttle_state tells us if I/O limits are configured. */
-    ThrottleState *throttle_state;
-    ThrottleTimers throttle_timers;
-    unsigned       pending_reqs[2];
-    QLIST_ENTRY(BlockBackendPublic) round_robin;
+    ThrottleGroupMember throttle_group_member;
 } BlockBackendPublic;
 
 BlockBackend *blk_new(uint64_t perm, uint64_t shared_perm);
diff --git a/tests/test-throttle.c b/tests/test-throttle.c
index 768f11dfed..6e6d926883 100644
--- a/tests/test-throttle.c
+++ b/tests/test-throttle.c
@@ -592,6 +592,7 @@ static void test_groups(void)
     ThrottleConfig cfg1, cfg2;
     BlockBackend *blk1, *blk2, *blk3;
     BlockBackendPublic *blkp1, *blkp2, *blkp3;
+    ThrottleGroupMember *tgm1, *tgm2, *tgm3;
 
     /* No actual I/O is performed on these devices */
     blk1 = blk_new(0, BLK_PERM_ALL);
@@ -602,21 +603,25 @@ static void test_groups(void)
     blkp2 = blk_get_public(blk2);
     blkp3 = blk_get_public(blk3);
 
-    g_assert(blkp1->throttle_state == NULL);
-    g_assert(blkp2->throttle_state == NULL);
-    g_assert(blkp3->throttle_state == NULL);
+    tgm1 = &blkp1->throttle_group_member;
+    tgm2 = &blkp2->throttle_group_member;
+    tgm3 = &blkp3->throttle_group_member;
 
-    throttle_group_register_blk(blk1, "bar");
-    throttle_group_register_blk(blk2, "foo");
-    throttle_group_register_blk(blk3, "bar");
+    g_assert(tgm1->throttle_state == NULL);
+    g_assert(tgm2->throttle_state == NULL);
+    g_assert(tgm3->throttle_state == NULL);
 
-    g_assert(blkp1->throttle_state != NULL);
-    g_assert(blkp2->throttle_state != NULL);
-    g_assert(blkp3->throttle_state != NULL);
+    throttle_group_register_tgm(tgm1, "bar");
+    throttle_group_register_tgm(tgm2, "foo");
+    throttle_group_register_tgm(tgm3, "bar");
 
-    g_assert(!strcmp(throttle_group_get_name(blk1), "bar"));
-    g_assert(!strcmp(throttle_group_get_name(blk2), "foo"));
-    g_assert(blkp1->throttle_state == blkp3->throttle_state);
+    g_assert(tgm1->throttle_state != NULL);
+    g_assert(tgm2->throttle_state != NULL);
+    g_assert(tgm3->throttle_state != NULL);
+
+    g_assert(!strcmp(throttle_group_get_name(tgm1), "bar"));
+    g_assert(!strcmp(throttle_group_get_name(tgm2), "foo"));
+    g_assert(tgm1->throttle_state == tgm3->throttle_state);
 
     /* Setting the config of a group member affects the whole group */
     throttle_config_init(&cfg1);
@@ -624,29 +629,29 @@ static void test_groups(void)
     cfg1.buckets[THROTTLE_BPS_WRITE].avg = 285000;
     cfg1.buckets[THROTTLE_OPS_READ].avg  = 20000;
     cfg1.buckets[THROTTLE_OPS_WRITE].avg = 12000;
-    throttle_group_config(blk1, &cfg1);
+    throttle_group_config(tgm1, &cfg1);
 
-    throttle_group_get_config(blk1, &cfg1);
-    throttle_group_get_config(blk3, &cfg2);
+    throttle_group_get_config(tgm1, &cfg1);
+    throttle_group_get_config(tgm3, &cfg2);
     g_assert(!memcmp(&cfg1, &cfg2, sizeof(cfg1)));
 
     cfg2.buckets[THROTTLE_BPS_READ].avg  = 4547;
     cfg2.buckets[THROTTLE_BPS_WRITE].avg = 1349;
     cfg2.buckets[THROTTLE_OPS_READ].avg  = 123;
     cfg2.buckets[THROTTLE_OPS_WRITE].avg = 86;
-    throttle_group_config(blk3, &cfg1);
+    throttle_group_config(tgm3, &cfg1);
 
-    throttle_group_get_config(blk1, &cfg1);
-    throttle_group_get_config(blk3, &cfg2);
+    throttle_group_get_config(tgm1, &cfg1);
+    throttle_group_get_config(tgm3, &cfg2);
     g_assert(!memcmp(&cfg1, &cfg2, sizeof(cfg1)));
 
-    throttle_group_unregister_blk(blk1);
-    throttle_group_unregister_blk(blk2);
-    throttle_group_unregister_blk(blk3);
+    throttle_group_unregister_tgm(tgm1);
+    throttle_group_unregister_tgm(tgm2);
+    throttle_group_unregister_tgm(tgm3);
 
-    g_assert(blkp1->throttle_state == NULL);
-    g_assert(blkp2->throttle_state == NULL);
-    g_assert(blkp3->throttle_state == NULL);
+    g_assert(tgm1->throttle_state == NULL);
+    g_assert(tgm2->throttle_state == NULL);
+    g_assert(tgm3->throttle_state == NULL);
 }
 
 int main(int argc, char **argv)
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [Qemu-devel] [PATCH 2/7] block: add aio_context field in ThrottleGroupMember
  2017-07-14  9:45 [Qemu-devel] [PATCH 0/7] add throttle block driver filter Manos Pitsidianakis
  2017-07-14  9:45 ` [Qemu-devel] [PATCH 1/7] block: move ThrottleGroup membership to ThrottleGroupMember Manos Pitsidianakis
@ 2017-07-14  9:45 ` Manos Pitsidianakis
  2017-07-24 14:38   ` Stefan Hajnoczi
  2017-07-14  9:45 ` [Qemu-devel] [PATCH 3/7] block: tidy ThrottleGroupMember initializations Manos Pitsidianakis
                   ` (4 subsequent siblings)
  6 siblings, 1 reply; 17+ messages in thread
From: Manos Pitsidianakis @ 2017-07-14  9:45 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Stefan Hajnoczi, Alberto Garcia, qemu-block

timer_cb() needs to know about the current Aio context of the throttle
request that is woken up. In order to make ThrottleGroupMember backend
agnostic, this information is stored in an aio_context field instead of
accessing it from BlockBackend.

Signed-off-by: Manos Pitsidianakis <el13635@mail.ntua.gr>
---
 block/block-backend.c           | 14 ++++-----
 block/throttle-groups.c         | 38 ++++++++++++++++---------
 include/block/throttle-groups.h |  7 ++++-
 tests/test-throttle.c           | 63 +++++++++++++++++++++--------------------
 4 files changed, 69 insertions(+), 53 deletions(-)

diff --git a/block/block-backend.c b/block/block-backend.c
index 30a60c0e7f..37b95dd20a 100644
--- a/block/block-backend.c
+++ b/block/block-backend.c
@@ -1686,17 +1686,15 @@ static AioContext *blk_aiocb_get_aio_context(BlockAIOCB *acb)
 void blk_set_aio_context(BlockBackend *blk, AioContext *new_context)
 {
     BlockDriverState *bs = blk_bs(blk);
-    ThrottleTimers *tt;
+    ThrottleGroupMember *tgm = &blk->public.throttle_group_member;
 
     if (bs) {
-        if (blk->public.throttle_group_member.throttle_state) {
-            tt = &blk->public.throttle_group_member.throttle_timers;
-            throttle_timers_detach_aio_context(tt);
+        if (tgm->throttle_state) {
+            throttle_group_detach_aio_context(tgm);
         }
         bdrv_set_aio_context(bs, new_context);
-        if (blk->public.throttle_group_member.throttle_state) {
-            tt = &blk->public.throttle_group_member.throttle_timers;
-            throttle_timers_attach_aio_context(tt, new_context);
+        if (tgm->throttle_state) {
+            throttle_group_attach_aio_context(tgm, new_context);
         }
     }
 }
@@ -1930,7 +1928,7 @@ void blk_io_limits_disable(BlockBackend *blk)
 void blk_io_limits_enable(BlockBackend *blk, const char *group)
 {
     assert(!blk->public.throttle_group_member.throttle_state);
-    throttle_group_register_tgm(&blk->public.throttle_group_member, group);
+    throttle_group_register_tgm(&blk->public.throttle_group_member, group, blk_get_aio_context(blk));
 }
 
 void blk_io_limits_update_group(BlockBackend *blk, const char *group)
diff --git a/block/throttle-groups.c b/block/throttle-groups.c
index c8ed16ddf8..a979e86243 100644
--- a/block/throttle-groups.c
+++ b/block/throttle-groups.c
@@ -391,9 +391,6 @@ static void coroutine_fn throttle_group_restart_queue_entry(void *opaque)
 
 static void throttle_group_restart_queue(ThrottleGroupMember *tgm, bool is_write)
 {
-    BlockBackendPublic *blkp = container_of(tgm, BlockBackendPublic,
-            throttle_group_member);
-    BlockBackend *blk = blk_by_public(blkp);
     Coroutine *co;
     RestartData rd = {
         .tgm = tgm,
@@ -401,7 +398,7 @@ static void throttle_group_restart_queue(ThrottleGroupMember *tgm, bool is_write
     };
 
     co = qemu_coroutine_create(throttle_group_restart_queue_entry, &rd);
-    aio_co_enter(blk_get_aio_context(blk), co);
+    aio_co_enter(tgm->aio_context, co);
 }
 
 void throttle_group_restart_tgm(ThrottleGroupMember *tgm)
@@ -449,13 +446,11 @@ void throttle_group_get_config(ThrottleGroupMember *tgm, ThrottleConfig *cfg)
 /* ThrottleTimers callback. This wakes up a request that was waiting
  * because it had been throttled.
  *
- * @blk:       the BlockBackend whose request had been throttled
+ * @tgm:       the ThrottleGroupMember whose request had been throttled
  * @is_write:  the type of operation (read/write)
  */
-static void timer_cb(BlockBackend *blk, bool is_write)
+static void timer_cb(ThrottleGroupMember *tgm, bool is_write)
 {
-    BlockBackendPublic *blkp = blk_get_public(blk);
-    ThrottleGroupMember *tgm = &blkp->throttle_group_member;
     ThrottleState *ts = tgm->throttle_state;
     ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts);
 
@@ -484,18 +479,18 @@ static void write_timer_cb(void *opaque)
  *
  * @tgm:       the ThrottleGroupMember to insert
  * @groupname: the name of the group
+ * @ctx:       the AioContext to use
  */
 void throttle_group_register_tgm(ThrottleGroupMember *tgm,
-                                 const char *groupname)
+                                 const char *groupname,
+                                 AioContext *ctx)
 {
     int i;
-    BlockBackendPublic *blkp = container_of(tgm, BlockBackendPublic,
-            throttle_group_member);
-    BlockBackend *blk = blk_by_public(blkp);
     ThrottleState *ts = throttle_group_incref(groupname);
     ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts);
 
     tgm->throttle_state = ts;
+    tgm->aio_context = ctx;
 
     qemu_mutex_lock(&tg->lock);
     /* If the ThrottleGroup is new set this ThrottleGroupMember as the token */
@@ -508,11 +503,11 @@ void throttle_group_register_tgm(ThrottleGroupMember *tgm,
     QLIST_INSERT_HEAD(&tg->head, tgm, round_robin);
 
     throttle_timers_init(&tgm->throttle_timers,
-                         blk_get_aio_context(blk),
+                         tgm->aio_context,
                          tg->clock_type,
                          read_timer_cb,
                          write_timer_cb,
-                         blk);
+                         tgm);
 
     qemu_mutex_unlock(&tg->lock);
 }
@@ -559,6 +554,21 @@ void throttle_group_unregister_tgm(ThrottleGroupMember *tgm)
     tgm->throttle_state = NULL;
 }
 
+void throttle_group_attach_aio_context(ThrottleGroupMember *tgm,
+                                       AioContext *new_context)
+{
+    ThrottleTimers *tt = &tgm->throttle_timers;
+    throttle_timers_attach_aio_context(tt, new_context);
+    tgm->aio_context = new_context;
+}
+
+void throttle_group_detach_aio_context(ThrottleGroupMember *tgm)
+{
+    tgm->aio_context = NULL;
+    ThrottleTimers *tt = &tgm->throttle_timers;
+    throttle_timers_detach_aio_context(tt);
+}
+
 static void throttle_groups_init(void)
 {
     qemu_mutex_init(&throttle_groups_lock);
diff --git a/include/block/throttle-groups.h b/include/block/throttle-groups.h
index 1a6bcdae74..a0f27cac63 100644
--- a/include/block/throttle-groups.h
+++ b/include/block/throttle-groups.h
@@ -33,6 +33,7 @@
  */
 
 typedef struct ThrottleGroupMember {
+    AioContext   *aio_context;
     /* throttled_reqs_lock protects the CoQueues for throttled requests.  */
     CoMutex      throttled_reqs_lock;
     CoQueue      throttled_reqs[2];
@@ -61,12 +62,16 @@ void throttle_group_config(ThrottleGroupMember *tgm, ThrottleConfig *cfg);
 void throttle_group_get_config(ThrottleGroupMember *tgm, ThrottleConfig *cfg);
 
 void throttle_group_register_tgm(ThrottleGroupMember *tgm,
-                                const char *groupname);
+                                const char *groupname,
+                                AioContext *ctx);
 void throttle_group_unregister_tgm(ThrottleGroupMember *tgm);
 void throttle_group_restart_tgm(ThrottleGroupMember *tgm);
 
 void coroutine_fn throttle_group_co_io_limits_intercept(ThrottleGroupMember *tgm,
                                                         unsigned int bytes,
                                                         bool is_write);
+void throttle_group_attach_aio_context(ThrottleGroupMember *tgm,
+                                       AioContext *new_context);
+void throttle_group_detach_aio_context(ThrottleGroupMember *tgm);
 
 #endif
diff --git a/tests/test-throttle.c b/tests/test-throttle.c
index 6e6d926883..57cf5ba711 100644
--- a/tests/test-throttle.c
+++ b/tests/test-throttle.c
@@ -24,8 +24,9 @@
 static AioContext     *ctx;
 static LeakyBucket    bkt;
 static ThrottleConfig cfg;
+static ThrottleGroupMember tgm;
 static ThrottleState  ts;
-static ThrottleTimers tt;
+static ThrottleTimers *tt;
 
 /* useful function */
 static bool double_cmp(double x, double y)
@@ -153,19 +154,21 @@ static void test_init(void)
 {
     int i;
 
+    tt = &tgm.throttle_timers;
+
     /* fill the structures with crap */
     memset(&ts, 1, sizeof(ts));
-    memset(&tt, 1, sizeof(tt));
+    memset(tt, 1, sizeof(*tt));
 
     /* init structures */
     throttle_init(&ts);
-    throttle_timers_init(&tt, ctx, QEMU_CLOCK_VIRTUAL,
+    throttle_timers_init(tt, ctx, QEMU_CLOCK_VIRTUAL,
                          read_timer_cb, write_timer_cb, &ts);
 
     /* check initialized fields */
-    g_assert(tt.clock_type == QEMU_CLOCK_VIRTUAL);
-    g_assert(tt.timers[0]);
-    g_assert(tt.timers[1]);
+    g_assert(tt->clock_type == QEMU_CLOCK_VIRTUAL);
+    g_assert(tt->timers[0]);
+    g_assert(tt->timers[1]);
 
     /* check other fields where cleared */
     g_assert(!ts.previous_leak);
@@ -176,18 +179,18 @@ static void test_init(void)
         g_assert(!ts.cfg.buckets[i].level);
     }
 
-    throttle_timers_destroy(&tt);
+    throttle_timers_destroy(tt);
 }
 
 static void test_destroy(void)
 {
     int i;
     throttle_init(&ts);
-    throttle_timers_init(&tt, ctx, QEMU_CLOCK_VIRTUAL,
+    throttle_timers_init(tt, ctx, QEMU_CLOCK_VIRTUAL,
                          read_timer_cb, write_timer_cb, &ts);
-    throttle_timers_destroy(&tt);
+    throttle_timers_destroy(tt);
     for (i = 0; i < 2; i++) {
-        g_assert(!tt.timers[i]);
+        g_assert(!tt->timers[i]);
     }
 }
 
@@ -224,7 +227,7 @@ static void test_config_functions(void)
     orig_cfg.op_size = 1;
 
     throttle_init(&ts);
-    throttle_timers_init(&tt, ctx, QEMU_CLOCK_VIRTUAL,
+    throttle_timers_init(tt, ctx, QEMU_CLOCK_VIRTUAL,
                          read_timer_cb, write_timer_cb, &ts);
     /* structure reset by throttle_init previous_leak should be null */
     g_assert(!ts.previous_leak);
@@ -236,7 +239,7 @@ static void test_config_functions(void)
     /* get back the fixed configuration */
     throttle_get_config(&ts, &final_cfg);
 
-    throttle_timers_destroy(&tt);
+    throttle_timers_destroy(tt);
 
     g_assert(final_cfg.buckets[THROTTLE_BPS_TOTAL].avg == 153);
     g_assert(final_cfg.buckets[THROTTLE_BPS_READ].avg  == 56);
@@ -417,45 +420,45 @@ static void test_have_timer(void)
 {
     /* zero structures */
     memset(&ts, 0, sizeof(ts));
-    memset(&tt, 0, sizeof(tt));
+    memset(tt, 0, sizeof(*tt));
 
     /* no timer set should return false */
-    g_assert(!throttle_timers_are_initialized(&tt));
+    g_assert(!throttle_timers_are_initialized(tt));
 
     /* init structures */
     throttle_init(&ts);
-    throttle_timers_init(&tt, ctx, QEMU_CLOCK_VIRTUAL,
+    throttle_timers_init(tt, ctx, QEMU_CLOCK_VIRTUAL,
                          read_timer_cb, write_timer_cb, &ts);
 
     /* timer set by init should return true */
-    g_assert(throttle_timers_are_initialized(&tt));
+    g_assert(throttle_timers_are_initialized(tt));
 
-    throttle_timers_destroy(&tt);
+    throttle_timers_destroy(tt);
 }
 
 static void test_detach_attach(void)
 {
     /* zero structures */
     memset(&ts, 0, sizeof(ts));
-    memset(&tt, 0, sizeof(tt));
+    memset(tt, 0, sizeof(*tt));
 
     /* init the structure */
     throttle_init(&ts);
-    throttle_timers_init(&tt, ctx, QEMU_CLOCK_VIRTUAL,
+    throttle_timers_init(tt, ctx, QEMU_CLOCK_VIRTUAL,
                          read_timer_cb, write_timer_cb, &ts);
 
     /* timer set by init should return true */
-    g_assert(throttle_timers_are_initialized(&tt));
+    g_assert(throttle_timers_are_initialized(tt));
 
     /* timer should no longer exist after detaching */
-    throttle_timers_detach_aio_context(&tt);
-    g_assert(!throttle_timers_are_initialized(&tt));
+    throttle_timers_detach_aio_context(tt);
+    g_assert(!throttle_timers_are_initialized(tt));
 
     /* timer should exist again after attaching */
-    throttle_timers_attach_aio_context(&tt, ctx);
-    g_assert(throttle_timers_are_initialized(&tt));
+    throttle_timers_attach_aio_context(tt, ctx);
+    g_assert(throttle_timers_are_initialized(tt));
 
-    throttle_timers_destroy(&tt);
+    throttle_timers_destroy(tt);
 }
 
 static bool do_test_accounting(bool is_ops, /* are we testing bps or ops */
@@ -484,7 +487,7 @@ static bool do_test_accounting(bool is_ops, /* are we testing bps or ops */
     cfg.op_size = op_size;
 
     throttle_init(&ts);
-    throttle_timers_init(&tt, ctx, QEMU_CLOCK_VIRTUAL,
+    throttle_timers_init(tt, ctx, QEMU_CLOCK_VIRTUAL,
                          read_timer_cb, write_timer_cb, &ts);
     throttle_config(&ts, QEMU_CLOCK_VIRTUAL, &cfg);
 
@@ -511,7 +514,7 @@ static bool do_test_accounting(bool is_ops, /* are we testing bps or ops */
         return false;
     }
 
-    throttle_timers_destroy(&tt);
+    throttle_timers_destroy(tt);
 
     return true;
 }
@@ -611,9 +614,9 @@ static void test_groups(void)
     g_assert(tgm2->throttle_state == NULL);
     g_assert(tgm3->throttle_state == NULL);
 
-    throttle_group_register_tgm(tgm1, "bar");
-    throttle_group_register_tgm(tgm2, "foo");
-    throttle_group_register_tgm(tgm3, "bar");
+    throttle_group_register_tgm(tgm1, "bar", blk_get_aio_context(blk1));
+    throttle_group_register_tgm(tgm2, "foo", blk_get_aio_context(blk2));
+    throttle_group_register_tgm(tgm3, "bar", blk_get_aio_context(blk3));
 
     g_assert(tgm1->throttle_state != NULL);
     g_assert(tgm2->throttle_state != NULL);
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [Qemu-devel] [PATCH 3/7] block: tidy ThrottleGroupMember initializations
  2017-07-14  9:45 [Qemu-devel] [PATCH 0/7] add throttle block driver filter Manos Pitsidianakis
  2017-07-14  9:45 ` [Qemu-devel] [PATCH 1/7] block: move ThrottleGroup membership to ThrottleGroupMember Manos Pitsidianakis
  2017-07-14  9:45 ` [Qemu-devel] [PATCH 2/7] block: add aio_context field in ThrottleGroupMember Manos Pitsidianakis
@ 2017-07-14  9:45 ` Manos Pitsidianakis
  2017-07-24 14:39   ` Stefan Hajnoczi
  2017-07-14  9:45 ` [Qemu-devel] [PATCH 4/7] block: convert ThrottleGroup to object with QOM Manos Pitsidianakis
                   ` (3 subsequent siblings)
  6 siblings, 1 reply; 17+ messages in thread
From: Manos Pitsidianakis @ 2017-07-14  9:45 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Stefan Hajnoczi, Alberto Garcia, qemu-block

Move the CoMutex and CoQueue inits inside throttle_group_register_tgm()
which is called whenever a ThrottleGroupMember is initialized. There's
no need for them to be separate.

Signed-off-by: Manos Pitsidianakis <el13635@mail.ntua.gr>
---
 block/block-backend.c   | 3 ---
 block/throttle-groups.c | 3 +++
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/block/block-backend.c b/block/block-backend.c
index 37b95dd20a..1c7f46fbaf 100644
--- a/block/block-backend.c
+++ b/block/block-backend.c
@@ -216,9 +216,6 @@ BlockBackend *blk_new(uint64_t perm, uint64_t shared_perm)
     blk->shared_perm = shared_perm;
     blk_set_enable_write_cache(blk, true);
 
-    qemu_co_mutex_init(&blk->public.throttle_group_member.throttled_reqs_lock);
-    qemu_co_queue_init(&blk->public.throttle_group_member.throttled_reqs[0]);
-    qemu_co_queue_init(&blk->public.throttle_group_member.throttled_reqs[1]);
     block_acct_init(&blk->stats);
 
     notifier_list_init(&blk->remove_bs_notifiers);
diff --git a/block/throttle-groups.c b/block/throttle-groups.c
index a979e86243..f711a3dc62 100644
--- a/block/throttle-groups.c
+++ b/block/throttle-groups.c
@@ -508,6 +508,9 @@ void throttle_group_register_tgm(ThrottleGroupMember *tgm,
                          read_timer_cb,
                          write_timer_cb,
                          tgm);
+    qemu_co_mutex_init(&tgm->throttled_reqs_lock);
+    qemu_co_queue_init(&tgm->throttled_reqs[0]);
+    qemu_co_queue_init(&tgm->throttled_reqs[1]);
 
     qemu_mutex_unlock(&tg->lock);
 }
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [Qemu-devel] [PATCH 4/7] block: convert ThrottleGroup to object with QOM
  2017-07-14  9:45 [Qemu-devel] [PATCH 0/7] add throttle block driver filter Manos Pitsidianakis
                   ` (2 preceding siblings ...)
  2017-07-14  9:45 ` [Qemu-devel] [PATCH 3/7] block: tidy ThrottleGroupMember initializations Manos Pitsidianakis
@ 2017-07-14  9:45 ` Manos Pitsidianakis
  2017-07-24 15:12   ` Stefan Hajnoczi
  2017-07-14  9:45 ` [Qemu-devel] [PATCH 5/7] block: add throttle block filter driver Manos Pitsidianakis
                   ` (2 subsequent siblings)
  6 siblings, 1 reply; 17+ messages in thread
From: Manos Pitsidianakis @ 2017-07-14  9:45 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Stefan Hajnoczi, Alberto Garcia, qemu-block

ThrottleGroup is converted to an object. This will allow the future
throttle block filter drive easy creation and configuration of throttle
groups in QMP and cli.

A new QAPI struct, ThrottleLimits, is introduced to provide a shared
struct for all throttle configuration needs in QMP.

ThrottleGroups can be created via CLI as
    -object throttling-group,id=foo,x-iops-total=100,x-..
where x-* are individual limit properties. Since we can't add non-scalar
properties in -object this interface must be used instead. However,
setting these properties must be disabled after initialization because
certain combinations of limits are forbidden and thus configuration
changes should be done in one transaction. The individual properties
will go away when support for non-scalar values in CLI is implemented
and thus are marked as experimental.

ThrottleGroup also has a `limits` property that uses the ThrottleLimits
struct.  It can be used to create ThrottleGroups or set the
configuration in existing groups as follows:

{ "execute": "object-add",
  "arguments": {
    "qom-type": "throttling-group",
    "id": "foo",
    "props" : {
      "limits": {
          "iops-total": 100
      }
    }
  }
}
{ "execute" : "qom-set",
    "arguments" : {
        "path" : "foo",
        "property" : "limits",
        "value" : {
            "iops-total" : 99
        }
    }
}

This also means a group's configuration can be fetched with qom-get.

ThrottleGroups can be anonymous which means they can't get accessed by
other users ie they will always be units instead of group (Because they
have one ThrottleGroupMember).

Signed-off-by: Manos Pitsidianakis <el13635@mail.ntua.gr>
---

Notes:
    Note: I tested Markus Armbruster's patch in <87wp7fghi9.fsf@dusky.pond.sub.org>
    on master and I can use this syntax successfuly:
    -object '{ "qom-type" : "throttling-group", "id" : "foo", "props" : { "limits" \
    : { "iops-total" : 1000 } } }'
    If this gets merged using -object will be a little more verbose but at least we
    won't have seperate properties, which is a good thing, so the x-* should be
    dropped.

 block/throttle-groups.c         | 507 +++++++++++++++++++++++++++++++++++++---
 include/block/throttle-groups.h |   3 +
 include/qemu/throttle-options.h |  59 +++--
 include/qemu/throttle.h         |   3 +
 qapi/block-core.json            |  45 ++++
 tests/test-throttle.c           |   1 +
 util/throttle.c                 | 121 ++++++++++
 7 files changed, 689 insertions(+), 50 deletions(-)

diff --git a/block/throttle-groups.c b/block/throttle-groups.c
index f711a3dc62..4d1f82ec06 100644
--- a/block/throttle-groups.c
+++ b/block/throttle-groups.c
@@ -25,9 +25,17 @@
 #include "qemu/osdep.h"
 #include "sysemu/block-backend.h"
 #include "block/throttle-groups.h"
+#include "qemu/throttle-options.h"
 #include "qemu/queue.h"
 #include "qemu/thread.h"
 #include "sysemu/qtest.h"
+#include "qapi/error.h"
+#include "qapi-visit.h"
+#include "qom/object.h"
+#include "qom/object_interfaces.h"
+
+static void throttle_group_obj_init(Object *obj);
+static void throttle_group_obj_complete(UserCreatable *obj, Error **errp);
 
 /* The ThrottleGroup structure (with its ThrottleState) is shared
  * among different ThrottleGroupMembers and it's independent from
@@ -54,6 +62,10 @@
  * that ThrottleGroupMember has throttled requests in the queue.
  */
 typedef struct ThrottleGroup {
+    Object parent_obj;
+
+    /* refuse individual property change if initialization is complete */
+    bool is_initialized;
     char *name; /* This is constant during the lifetime of the group */
 
     QemuMutex lock; /* This lock protects the following four fields */
@@ -63,8 +75,7 @@ typedef struct ThrottleGroup {
     bool any_timer_armed[2];
     QEMUClockType clock_type;
 
-    /* These two are protected by the global throttle_groups_lock */
-    unsigned refcount;
+    /* This is protected by the global throttle_groups_lock */
     QTAILQ_ENTRY(ThrottleGroup) list;
 } ThrottleGroup;
 
@@ -77,7 +88,8 @@ static QTAILQ_HEAD(, ThrottleGroup) throttle_groups =
  * If no ThrottleGroup is found with the given name a new one is
  * created.
  *
- * @name: the name of the ThrottleGroup
+ * @name: the name of the ThrottleGroup, NULL means a new anonymous group will
+ *        be created.
  * @ret:  the ThrottleState member of the ThrottleGroup
  */
 ThrottleState *throttle_group_incref(const char *name)
@@ -87,32 +99,30 @@ ThrottleState *throttle_group_incref(const char *name)
 
     qemu_mutex_lock(&throttle_groups_lock);
 
-    /* Look for an existing group with that name */
-    QTAILQ_FOREACH(iter, &throttle_groups, list) {
-        if (!strcmp(name, iter->name)) {
-            tg = iter;
-            break;
+    if (name) {
+        /* Look for an existing group with that name */
+        QTAILQ_FOREACH(iter, &throttle_groups, list) {
+            if (!g_strcmp0(name, iter->name)) {
+                tg = iter;
+                break;
+            }
         }
     }
 
     /* Create a new one if not found */
     if (!tg) {
-        tg = g_new0(ThrottleGroup, 1);
+        /* new ThrottleGroup obj will have a refcnt = 1 */
+        tg = THROTTLE_GROUP(object_new(TYPE_THROTTLE_GROUP));
         tg->name = g_strdup(name);
-        tg->clock_type = QEMU_CLOCK_REALTIME;
-
-        if (qtest_enabled()) {
-            /* For testing block IO throttling only */
-            tg->clock_type = QEMU_CLOCK_VIRTUAL;
-        }
-        qemu_mutex_init(&tg->lock);
-        throttle_init(&tg->ts);
-        QLIST_INIT(&tg->head);
-
-        QTAILQ_INSERT_TAIL(&throttle_groups, tg, list);
+        throttle_group_obj_complete((UserCreatable *)tg, &error_abort);
     }
 
-    tg->refcount++;
+    qemu_mutex_lock(&tg->lock);
+    if (!QLIST_EMPTY(&tg->head)) {
+        /* only ref if the group is not empty */
+        object_ref(OBJECT(tg));
+    }
+    qemu_mutex_unlock(&tg->lock);
 
     qemu_mutex_unlock(&throttle_groups_lock);
 
@@ -129,15 +139,7 @@ ThrottleState *throttle_group_incref(const char *name)
 void throttle_group_unref(ThrottleState *ts)
 {
     ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts);
-
-    qemu_mutex_lock(&throttle_groups_lock);
-    if (--tg->refcount == 0) {
-        QTAILQ_REMOVE(&throttle_groups, tg, list);
-        qemu_mutex_destroy(&tg->lock);
-        g_free(tg->name);
-        g_free(tg);
-    }
-    qemu_mutex_unlock(&throttle_groups_lock);
+    object_unref(OBJECT(tg));
 }
 
 /* Get the name from a ThrottleGroupMember's group. The name (and the pointer)
@@ -572,9 +574,452 @@ void throttle_group_detach_aio_context(ThrottleGroupMember *tgm)
     throttle_timers_detach_aio_context(tt);
 }
 
+#define DOUBLE 0
+#define UINT64 1
+#define UNSIGNED 2
+
+typedef struct {
+    BucketType type;
+    int data_type;
+    ptrdiff_t offset; /* offset in LeakyBucket struct. */
+} ThrottleParamInfo;
+
+static ThrottleParamInfo throttle_iops_total_info = {
+    THROTTLE_OPS_TOTAL, DOUBLE, offsetof(LeakyBucket, avg),
+};
+
+static ThrottleParamInfo throttle_iops_total_max_info = {
+    THROTTLE_OPS_TOTAL, DOUBLE, offsetof(LeakyBucket, max),
+};
+
+static ThrottleParamInfo throttle_iops_total_max_length_info = {
+    THROTTLE_OPS_TOTAL, UNSIGNED, offsetof(LeakyBucket, burst_length),
+};
+
+static ThrottleParamInfo throttle_iops_read_info = {
+    THROTTLE_OPS_READ, DOUBLE, offsetof(LeakyBucket, avg),
+};
+
+static ThrottleParamInfo throttle_iops_read_max_info = {
+    THROTTLE_OPS_READ, DOUBLE, offsetof(LeakyBucket, max),
+};
+
+static ThrottleParamInfo throttle_iops_read_max_length_info = {
+    THROTTLE_OPS_READ, UNSIGNED, offsetof(LeakyBucket, burst_length),
+};
+
+static ThrottleParamInfo throttle_iops_write_info = {
+    THROTTLE_OPS_WRITE, DOUBLE, offsetof(LeakyBucket, avg),
+};
+
+static ThrottleParamInfo throttle_iops_write_max_info = {
+    THROTTLE_OPS_WRITE, DOUBLE, offsetof(LeakyBucket, max),
+};
+
+static ThrottleParamInfo throttle_iops_write_max_length_info = {
+    THROTTLE_OPS_WRITE, UNSIGNED, offsetof(LeakyBucket, burst_length),
+};
+
+static ThrottleParamInfo throttle_bps_total_info = {
+    THROTTLE_BPS_TOTAL, DOUBLE, offsetof(LeakyBucket, avg),
+};
+
+static ThrottleParamInfo throttle_bps_total_max_info = {
+    THROTTLE_BPS_TOTAL, DOUBLE, offsetof(LeakyBucket, max),
+};
+
+static ThrottleParamInfo throttle_bps_total_max_length_info = {
+    THROTTLE_BPS_TOTAL, UNSIGNED, offsetof(LeakyBucket, burst_length),
+};
+
+static ThrottleParamInfo throttle_bps_read_info = {
+    THROTTLE_BPS_READ, DOUBLE, offsetof(LeakyBucket, avg),
+};
+
+static ThrottleParamInfo throttle_bps_read_max_info = {
+    THROTTLE_BPS_READ, DOUBLE, offsetof(LeakyBucket, max),
+};
+
+static ThrottleParamInfo throttle_bps_read_max_length_info = {
+    THROTTLE_BPS_READ, UNSIGNED, offsetof(LeakyBucket, burst_length),
+};
+
+static ThrottleParamInfo throttle_bps_write_info = {
+    THROTTLE_BPS_WRITE, DOUBLE, offsetof(LeakyBucket, avg),
+};
+
+static ThrottleParamInfo throttle_bps_write_max_info = {
+    THROTTLE_BPS_WRITE, DOUBLE, offsetof(LeakyBucket, max),
+};
+
+static ThrottleParamInfo throttle_bps_write_max_length_info = {
+    THROTTLE_BPS_WRITE, UNSIGNED, offsetof(LeakyBucket, burst_length),
+};
+
+static ThrottleParamInfo throttle_iops_size_info = {
+    0, UINT64, offsetof(ThrottleConfig, op_size),
+};
+
+static void throttle_group_obj_init(Object *obj)
+{
+    ThrottleGroup *tg = THROTTLE_GROUP(obj);
+
+    tg->clock_type = QEMU_CLOCK_REALTIME;
+    if (qtest_enabled()) {
+        /* For testing block IO throttling only */
+        tg->clock_type = QEMU_CLOCK_VIRTUAL;
+    }
+    tg->is_initialized = false;
+    qemu_mutex_init(&tg->lock);
+    throttle_init(&tg->ts);
+    QLIST_INIT(&tg->head);
+}
+
+static void throttle_group_obj_complete(UserCreatable *obj, Error **errp)
+{
+    ThrottleGroup *tg = THROTTLE_GROUP(obj), *iter;
+    ThrottleConfig *cfg = &tg->ts.cfg;
+
+    /* set group name to object id if it exists */
+    if (!tg->name && tg->parent_obj.parent) {
+        tg->name = object_get_canonical_path_component(OBJECT(obj));
+    }
+
+    if (tg->name) {
+        /* error if name is duplicate */
+        QTAILQ_FOREACH(iter, &throttle_groups, list) {
+            if (!g_strcmp0(tg->name, iter->name)) {
+                error_setg(errp, "A group with this name already exists");
+                goto fail;
+            }
+        }
+    }
+
+    /* unfix buckets to check validity */
+    throttle_get_config(&tg->ts, cfg);
+    if (!throttle_is_valid(cfg, errp)) {
+        goto fail;
+    }
+    /* fix buckets again */
+    throttle_config(&tg->ts, tg->clock_type, cfg);
+
+    tg->is_initialized = true;
+    QTAILQ_INSERT_TAIL(&throttle_groups, tg, list);
+    return;
+fail:
+    qemu_mutex_destroy(&tg->lock);
+    g_free(tg->name);
+}
+
+static void throttle_group_obj_finalize(Object *obj)
+{
+    ThrottleGroup *tg = THROTTLE_GROUP(obj);
+    qemu_mutex_lock(&throttle_groups_lock);
+    QTAILQ_REMOVE(&throttle_groups, tg, list);
+    qemu_mutex_unlock(&throttle_groups_lock);
+    qemu_mutex_destroy(&tg->lock);
+    g_free(tg->name);
+}
+
+static void throttle_group_set(Object *obj, Visitor *v, const char * name,
+        void *opaque, Error **errp)
+
+{
+    ThrottleGroup *tg = THROTTLE_GROUP(obj);
+    ThrottleConfig cfg;
+    ThrottleParamInfo *info = opaque;
+    ThrottleLimits *arg = NULL;
+    Error *local_err = NULL;
+    int64_t value;
+
+    if (!info) {
+        arg = g_new0(ThrottleLimits, 1);
+        visit_type_ThrottleLimits(v, name, &arg, &local_err);
+        if (local_err) {
+            goto fail;
+        }
+        qemu_mutex_lock(&tg->lock);
+        throttle_get_config(&tg->ts, &cfg);
+        throttle_limits_to_config(arg, &cfg, &local_err);
+        if (local_err) {
+            qemu_mutex_unlock(&tg->lock);
+            goto fail;
+        }
+        throttle_config(&tg->ts, tg->clock_type, &cfg);
+        qemu_mutex_unlock(&tg->lock);
+
+        goto ret;
+    }
+
+    /* If we have finished initialization, don't accept individual property
+     * changes through QOM. Throttle configuration limits must be set in one
+     * transaction, as certain combinations are invalid.
+     */
+    if (tg->is_initialized) {
+        error_setg(&local_err, "Property cannot be set after initialization");
+        goto fail;
+    }
+
+    visit_type_int64(v, name, &value, &local_err);
+    if (local_err) {
+        goto fail;
+    }
+    if (value < 0) {
+        error_setg(&local_err, "Property value must be in range "
+                               "[0, %"PRId64"]", INT64_MAX);
+        goto fail;
+    }
+
+    cfg = tg->ts.cfg;
+    switch (info->data_type) {
+    case UINT64:
+        {
+            uint64_t *field = (void *)&cfg.buckets[info->type] + info->offset;
+            *field = value;
+        }
+        break;
+    case DOUBLE:
+        {
+            double *field = (void *)&cfg.buckets[info->type] + info->offset;
+            *field = value;
+        }
+        break;
+    case UNSIGNED:
+        {
+            unsigned *field = (void *)&cfg.buckets[info->type] + info->offset;
+            *field = value;
+        }
+    }
+
+    tg->ts.cfg = cfg;
+    goto ret;
+
+fail:
+    error_propagate(errp, local_err);
+ret:
+    g_free(arg);
+    return;
+
+}
+
+static void throttle_group_get(Object *obj, Visitor *v, const char *name,
+        void *opaque, Error **errp)
+{
+    ThrottleGroup *tg = THROTTLE_GROUP(obj);
+    ThrottleConfig cfg;
+    ThrottleParamInfo *info = opaque;
+    ThrottleLimits *arg = NULL;
+    int64_t value;
+
+    if (!info) {
+        arg = g_new0(ThrottleLimits, 1);
+        qemu_mutex_lock(&tg->lock);
+        throttle_get_config(&tg->ts, &cfg);
+        qemu_mutex_unlock(&tg->lock);
+        throttle_config_to_throttle_limits(&cfg, arg);
+        visit_type_ThrottleLimits(v, name, &arg, errp);
+        g_free(arg);
+        return;
+    }
+
+    cfg = tg->ts.cfg;
+    switch (info->data_type) {
+    case UINT64:
+        {
+            uint64_t *field = (void *)&cfg.buckets[info->type] + info->offset;
+            value = *field;
+        }
+        break;
+    case DOUBLE:
+        {
+            double *field = (void *)&cfg.buckets[info->type] + info->offset;
+            value = *field;
+        }
+        break;
+    case UNSIGNED:
+        {
+            unsigned *field = (void *)&cfg.buckets[info->type] + info->offset;
+            value = *field;
+        }
+    }
+
+    visit_type_int64(v, name, &value, errp);
+}
+
+#undef THROTTLE_OPT_PREFIX
+#define THROTTLE_OPT_PREFIX "x-"
+static void throttle_group_obj_class_init(ObjectClass *klass, void *class_data)
+{
+    UserCreatableClass *ucc = USER_CREATABLE_CLASS(klass);
+
+    ucc->complete = throttle_group_obj_complete;
+    /* iops limits */
+    object_class_property_add(klass,
+                              THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_TOTAL,
+                              "int",
+                              throttle_group_get,
+                              throttle_group_set,
+                              NULL, &throttle_iops_total_info,
+                              &error_abort);
+    object_class_property_add(klass,
+                              THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_TOTAL_MAX,
+                              "int",
+                              throttle_group_get,
+                              throttle_group_set,
+                              NULL, &throttle_iops_total_max_info,
+                              &error_abort);
+    object_class_property_add(klass,
+                              THROTTLE_OPT_PREFIX
+                              QEMU_OPT_IOPS_TOTAL_MAX_LENGTH,
+                              "int",
+                              throttle_group_get,
+                              throttle_group_set,
+                              NULL, &throttle_iops_total_max_length_info,
+                              &error_abort);
+    object_class_property_add(klass,
+                              THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_READ,
+                              "int",
+                              throttle_group_get,
+                              throttle_group_set,
+                              NULL, &throttle_iops_read_info,
+                              &error_abort);
+    object_class_property_add(klass,
+                              THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_READ_MAX,
+                              "int",
+                              throttle_group_get,
+                              throttle_group_set,
+                              NULL, &throttle_iops_read_max_info,
+                              &error_abort);
+    object_class_property_add(klass,
+                              THROTTLE_OPT_PREFIX
+                              QEMU_OPT_IOPS_READ_MAX_LENGTH,
+                              "int",
+                              throttle_group_get,
+                              throttle_group_set,
+                              NULL, &throttle_iops_read_max_length_info,
+                              &error_abort);
+    object_class_property_add(klass,
+                              THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_WRITE,
+                              "int",
+                              throttle_group_get,
+                              throttle_group_set,
+                              NULL, &throttle_iops_write_info, &error_abort);
+    object_class_property_add(klass,
+                              THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_WRITE_MAX,
+                              "int",
+                              throttle_group_get,
+                              throttle_group_set,
+                              NULL, &throttle_iops_write_max_info,
+                              &error_abort);
+    object_class_property_add(klass,
+                              THROTTLE_OPT_PREFIX
+                              QEMU_OPT_IOPS_WRITE_MAX_LENGTH,
+                              "int",
+                              throttle_group_get,
+                              throttle_group_set,
+                              NULL, &throttle_iops_write_max_length_info,
+                              &error_abort);
+    /* bps limits */
+    object_class_property_add(klass,
+                              THROTTLE_OPT_PREFIX QEMU_OPT_BPS_TOTAL,
+                              "int",
+                              throttle_group_get,
+                              throttle_group_set,
+                              NULL, &throttle_bps_total_info,
+                              &error_abort);
+    object_class_property_add(klass,
+                              THROTTLE_OPT_PREFIX QEMU_OPT_BPS_TOTAL_MAX,
+                              "int",
+                              throttle_group_get,
+                              throttle_group_set,
+                              NULL, &throttle_bps_total_max_info,
+                              &error_abort);
+    object_class_property_add(klass,
+                              THROTTLE_OPT_PREFIX
+                              QEMU_OPT_BPS_TOTAL_MAX_LENGTH,
+                              "int",
+                              throttle_group_get,
+                              throttle_group_set,
+                              NULL, &throttle_bps_total_max_length_info,
+                              &error_abort);
+    object_class_property_add(klass,
+                              THROTTLE_OPT_PREFIX QEMU_OPT_BPS_READ,
+                              "int",
+                              throttle_group_get,
+                              throttle_group_set,
+                              NULL, &throttle_bps_read_info,
+                              &error_abort);
+    object_class_property_add(klass,
+                              THROTTLE_OPT_PREFIX QEMU_OPT_BPS_READ_MAX,
+                              "int",
+                              throttle_group_get,
+                              throttle_group_set,
+                              NULL, &throttle_bps_read_max_info,
+                              &error_abort);
+    object_class_property_add(klass,
+                              THROTTLE_OPT_PREFIX QEMU_OPT_BPS_READ_MAX_LENGTH,
+                              "int",
+                              throttle_group_get,
+                              throttle_group_set,
+                              NULL, &throttle_bps_read_max_length_info,
+                              &error_abort);
+    object_class_property_add(klass,
+                              THROTTLE_OPT_PREFIX QEMU_OPT_BPS_WRITE,
+                              "int",
+                              throttle_group_get,
+                              throttle_group_set,
+                              NULL, &throttle_bps_write_info,
+                              &error_abort);
+    object_class_property_add(klass,
+                              THROTTLE_OPT_PREFIX QEMU_OPT_BPS_WRITE_MAX,
+                              "int",
+                              throttle_group_get,
+                              throttle_group_set,
+                              NULL, &throttle_bps_write_max_info,
+                              &error_abort);
+    object_class_property_add(klass,
+                              THROTTLE_OPT_PREFIX
+                              QEMU_OPT_BPS_WRITE_MAX_LENGTH,
+                              "int",
+                              throttle_group_get,
+                              throttle_group_set,
+                              NULL, &throttle_bps_write_max_length_info,
+                              &error_abort);
+    /* rest */
+    object_class_property_add(klass,
+                              THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_SIZE,
+                              "int",
+                              throttle_group_get,
+                              throttle_group_set,
+                              NULL, &throttle_iops_size_info,
+                              &error_abort);
+
+    /* ThrottleLimits */
+    object_class_property_add(klass,
+                              "limits", "ThrottleLimits",
+                              throttle_group_get,
+                              throttle_group_set,
+                              NULL, NULL,
+                              &error_abort);
+}
+
+static const TypeInfo throttle_group_info = {
+   .name = TYPE_THROTTLE_GROUP,
+   .parent = TYPE_OBJECT,
+   .class_init = throttle_group_obj_class_init,
+   .instance_size = sizeof(ThrottleGroup),
+   .instance_init = throttle_group_obj_init,
+   .instance_finalize = throttle_group_obj_finalize,
+   .interfaces = (InterfaceInfo[]) {
+       { TYPE_USER_CREATABLE },
+       { }
+   },
+};
+
 static void throttle_groups_init(void)
 {
     qemu_mutex_init(&throttle_groups_lock);
+    type_register_static(&throttle_group_info);
 }
 
-block_init(throttle_groups_init);
+type_init(throttle_groups_init);
diff --git a/include/block/throttle-groups.h b/include/block/throttle-groups.h
index a0f27cac63..096d05ef92 100644
--- a/include/block/throttle-groups.h
+++ b/include/block/throttle-groups.h
@@ -53,6 +53,9 @@ typedef struct ThrottleGroupMember {
 
 } ThrottleGroupMember;
 
+#define TYPE_THROTTLE_GROUP "throttling-group"
+#define THROTTLE_GROUP(obj) OBJECT_CHECK(ThrottleGroup, (obj), TYPE_THROTTLE_GROUP)
+
 const char *throttle_group_get_name(ThrottleGroupMember *tgm);
 
 ThrottleState *throttle_group_incref(const char *name);
diff --git a/include/qemu/throttle-options.h b/include/qemu/throttle-options.h
index 3133d1ca40..182b7896e1 100644
--- a/include/qemu/throttle-options.h
+++ b/include/qemu/throttle-options.h
@@ -10,81 +10,102 @@
 #ifndef THROTTLE_OPTIONS_H
 #define THROTTLE_OPTIONS_H
 
+#define QEMU_OPT_IOPS_TOTAL "iops-total"
+#define QEMU_OPT_IOPS_TOTAL_MAX "iops-total-max"
+#define QEMU_OPT_IOPS_TOTAL_MAX_LENGTH "iops-total-max-length"
+#define QEMU_OPT_IOPS_READ "iops-read"
+#define QEMU_OPT_IOPS_READ_MAX "iops-read-max"
+#define QEMU_OPT_IOPS_READ_MAX_LENGTH "iops-read-max-length"
+#define QEMU_OPT_IOPS_WRITE "iops-write"
+#define QEMU_OPT_IOPS_WRITE_MAX "iops-write-max"
+#define QEMU_OPT_IOPS_WRITE_MAX_LENGTH "iops-write-max-length"
+#define QEMU_OPT_BPS_TOTAL "bps-total"
+#define QEMU_OPT_BPS_TOTAL_MAX "bps-total-max"
+#define QEMU_OPT_BPS_TOTAL_MAX_LENGTH "bps-total-max-length"
+#define QEMU_OPT_BPS_READ "bps-read"
+#define QEMU_OPT_BPS_READ_MAX "bps-read-max"
+#define QEMU_OPT_BPS_READ_MAX_LENGTH "bps-read-max-length"
+#define QEMU_OPT_BPS_WRITE "bps-write"
+#define QEMU_OPT_BPS_WRITE_MAX "bps-write-max"
+#define QEMU_OPT_BPS_WRITE_MAX_LENGTH "bps-write-max-length"
+#define QEMU_OPT_IOPS_SIZE "iops-size"
+
+#define THROTTLE_OPT_PREFIX "throttling."
 #define THROTTLE_OPTS \
           { \
-            .name = "throttling.iops-total",\
+            .name = THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_TOTAL,\
             .type = QEMU_OPT_NUMBER,\
             .help = "limit total I/O operations per second",\
         },{ \
-            .name = "throttling.iops-read",\
+            .name = THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_READ,\
             .type = QEMU_OPT_NUMBER,\
             .help = "limit read operations per second",\
         },{ \
-            .name = "throttling.iops-write",\
+            .name = THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_WRITE,\
             .type = QEMU_OPT_NUMBER,\
             .help = "limit write operations per second",\
         },{ \
-            .name = "throttling.bps-total",\
+            .name = THROTTLE_OPT_PREFIX QEMU_OPT_BPS_TOTAL,\
             .type = QEMU_OPT_NUMBER,\
             .help = "limit total bytes per second",\
         },{ \
-            .name = "throttling.bps-read",\
+            .name = THROTTLE_OPT_PREFIX QEMU_OPT_BPS_READ,\
             .type = QEMU_OPT_NUMBER,\
             .help = "limit read bytes per second",\
         },{ \
-            .name = "throttling.bps-write",\
+            .name = THROTTLE_OPT_PREFIX QEMU_OPT_BPS_WRITE,\
             .type = QEMU_OPT_NUMBER,\
             .help = "limit write bytes per second",\
         },{ \
-            .name = "throttling.iops-total-max",\
+            .name = THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_TOTAL_MAX,\
             .type = QEMU_OPT_NUMBER,\
             .help = "I/O operations burst",\
         },{ \
-            .name = "throttling.iops-read-max",\
+            .name = THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_READ_MAX,\
             .type = QEMU_OPT_NUMBER,\
             .help = "I/O operations read burst",\
         },{ \
-            .name = "throttling.iops-write-max",\
+            .name = THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_WRITE_MAX,\
             .type = QEMU_OPT_NUMBER,\
             .help = "I/O operations write burst",\
         },{ \
-            .name = "throttling.bps-total-max",\
+            .name = THROTTLE_OPT_PREFIX QEMU_OPT_BPS_TOTAL_MAX,\
             .type = QEMU_OPT_NUMBER,\
             .help = "total bytes burst",\
         },{ \
-            .name = "throttling.bps-read-max",\
+            .name = THROTTLE_OPT_PREFIX QEMU_OPT_BPS_READ_MAX,\
             .type = QEMU_OPT_NUMBER,\
             .help = "total bytes read burst",\
         },{ \
-            .name = "throttling.bps-write-max",\
+            .name = THROTTLE_OPT_PREFIX QEMU_OPT_BPS_WRITE_MAX,\
             .type = QEMU_OPT_NUMBER,\
             .help = "total bytes write burst",\
         },{ \
-            .name = "throttling.iops-total-max-length",\
+            .name = THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_TOTAL_MAX_LENGTH,\
             .type = QEMU_OPT_NUMBER,\
             .help = "length of the iops-total-max burst period, in seconds",\
         },{ \
-            .name = "throttling.iops-read-max-length",\
+            .name = THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_READ_MAX_LENGTH,\
             .type = QEMU_OPT_NUMBER,\
             .help = "length of the iops-read-max burst period, in seconds",\
         },{ \
-            .name = "throttling.iops-write-max-length",\
+            .name = THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_WRITE_MAX_LENGTH,\
             .type = QEMU_OPT_NUMBER,\
             .help = "length of the iops-write-max burst period, in seconds",\
         },{ \
-            .name = "throttling.bps-total-max-length",\
+            .name = THROTTLE_OPT_PREFIX QEMU_OPT_BPS_TOTAL_MAX_LENGTH,\
             .type = QEMU_OPT_NUMBER,\
             .help = "length of the bps-total-max burst period, in seconds",\
         },{ \
-            .name = "throttling.bps-read-max-length",\
+            .name = THROTTLE_OPT_PREFIX QEMU_OPT_BPS_READ_MAX_LENGTH,\
             .type = QEMU_OPT_NUMBER,\
             .help = "length of the bps-read-max burst period, in seconds",\
         },{ \
-            .name = "throttling.bps-write-max-length",\
+            .name = THROTTLE_OPT_PREFIX QEMU_OPT_BPS_WRITE_MAX_LENGTH,\
             .type = QEMU_OPT_NUMBER,\
             .help = "length of the bps-write-max burst period, in seconds",\
         },{ \
-            .name = "throttling.iops-size",\
+            .name = THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_SIZE,\
             .type = QEMU_OPT_NUMBER,\
             .help = "when limiting by iops max size of an I/O in bytes",\
         }
diff --git a/include/qemu/throttle.h b/include/qemu/throttle.h
index d056008c18..17e750b12d 100644
--- a/include/qemu/throttle.h
+++ b/include/qemu/throttle.h
@@ -152,5 +152,8 @@ bool throttle_schedule_timer(ThrottleState *ts,
                              bool is_write);
 
 void throttle_account(ThrottleState *ts, bool is_write, uint64_t size);
+void throttle_limits_to_config(ThrottleLimits *arg, ThrottleConfig *cfg,
+                               Error **errp);
+void throttle_config_to_throttle_limits(ThrottleConfig *cfg, ThrottleLimits *var);
 
 #endif
diff --git a/qapi/block-core.json b/qapi/block-core.json
index c437aa50ef..1084158b6a 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -1896,6 +1896,51 @@
             '*iops_size': 'int', '*group': 'str' } }
 
 ##
+# @ThrottleLimits:
+#
+# Limit parameters for throttling.
+#
+# @iops-total:             limit total I/O operations per second
+# @iops-total-max:         I/O operations burst
+# @iops-total-max-length:  length of the iops-total-max burst period, in seconds
+#                          It must only be set if @iops-total-max is set as well.
+# @iops-read:              limit read operations per second
+# @iops-read-max:          I/O operations read burst
+# @iops-read-max-length:   length of the iops-read-max burst period, in seconds
+#                          It must only be set if @iops-read-max is set as well.
+# @iops-write:             limit write operations per second
+# @iops-write-max:         I/O operations write burst
+# @iops-write-max-length:  length of the iops-write-max burst period, in seconds
+#                          It must only be set if @iops-write-max is set as well.
+# @bps-total:              limit total bytes per second
+# @bps-total-max:          total bytes burst
+# @bps-total-max-length:   length of the bps-total-max burst period, in seconds.
+#                          It must only be set if @bps-total-max is set as well.
+# @bps-read:               limit read bytes per second
+# @bps-read-max:           total bytes read burst
+# @bps-read-max-length:    length of the bps-read-max burst period, in seconds
+#                          It must only be set if @bps-read-max is set as well.
+# @bps-write:              limit write bytes per second
+# @bps-write-max:          total bytes write burst
+# @bps-write-max-length:   length of the bps-write-max burst period, in seconds
+#                          It must only be set if @bps-write-max is set as well.
+# @iops-size:              when limiting by iops max size of an I/O in bytes
+#
+# Since: 2.10
+##
+{ 'struct': 'ThrottleLimits',
+  'data': { '*iops-total' : 'int', '*iops-total-max' : 'int',
+            '*iops-total-max-length' : 'int', '*iops-read' : 'int',
+            '*iops-read-max' : 'int', '*iops-read-max-length' : 'int',
+            '*iops-write' : 'int', '*iops-write-max' : 'int',
+            '*iops-write-max-length' : 'int', '*bps-total' : 'int',
+            '*bps-total-max' : 'int', '*bps-total-max-length' : 'int',
+            '*bps-read' : 'int', '*bps-read-max' : 'int',
+            '*bps-read-max-length' : 'int', '*bps-write' : 'int',
+            '*bps-write-max' : 'int', '*bps-write-max-length' : 'int',
+            '*iops-size' : 'int' } }
+
+##
 # @block-stream:
 #
 # Copy data from a backing file into a block device.
diff --git a/tests/test-throttle.c b/tests/test-throttle.c
index 57cf5ba711..0ea9093eee 100644
--- a/tests/test-throttle.c
+++ b/tests/test-throttle.c
@@ -662,6 +662,7 @@ int main(int argc, char **argv)
     qemu_init_main_loop(&error_fatal);
     ctx = qemu_get_aio_context();
     bdrv_init();
+    module_call_init(MODULE_INIT_QOM);
 
     do {} while (g_main_context_iteration(NULL, false));
 
diff --git a/util/throttle.c b/util/throttle.c
index b2a52b8b34..3bb21a63e6 100644
--- a/util/throttle.c
+++ b/util/throttle.c
@@ -502,3 +502,124 @@ void throttle_account(ThrottleState *ts, bool is_write, uint64_t size)
     }
 }
 
+/* return a ThrottleConfig based on the options in a ThrottleLimits
+ *
+ * @arg:    the ThrottleLimits object to read from
+ * @cfg:    the ThrottleConfig to edit
+ * @errp:   error object
+ */
+void throttle_limits_to_config(ThrottleLimits *arg, ThrottleConfig *cfg,
+                               Error **errp)
+{
+    if (arg->has_bps_total) {
+        cfg->buckets[THROTTLE_BPS_TOTAL].avg = arg->bps_total;
+    }
+    if (arg->has_bps_read) {
+        cfg->buckets[THROTTLE_BPS_READ].avg  = arg->bps_read;
+    }
+    if (arg->has_bps_write) {
+        cfg->buckets[THROTTLE_BPS_WRITE].avg = arg->bps_write;
+    }
+
+    if (arg->has_iops_total) {
+        cfg->buckets[THROTTLE_OPS_TOTAL].avg = arg->iops_total;
+    }
+    if (arg->has_iops_read) {
+        cfg->buckets[THROTTLE_OPS_READ].avg  = arg->iops_read;
+    }
+    if (arg->has_iops_write) {
+        cfg->buckets[THROTTLE_OPS_WRITE].avg = arg->iops_write;
+    }
+
+    if (arg->has_bps_total_max) {
+        cfg->buckets[THROTTLE_BPS_TOTAL].max = arg->bps_total_max;
+    }
+    if (arg->has_bps_read_max) {
+        cfg->buckets[THROTTLE_BPS_READ].max = arg->bps_read_max;
+    }
+    if (arg->has_bps_write_max) {
+        cfg->buckets[THROTTLE_BPS_WRITE].max = arg->bps_write_max;
+    }
+    if (arg->has_iops_total_max) {
+        cfg->buckets[THROTTLE_OPS_TOTAL].max = arg->iops_total_max;
+    }
+    if (arg->has_iops_read_max) {
+        cfg->buckets[THROTTLE_OPS_READ].max = arg->iops_read_max;
+    }
+    if (arg->has_iops_write_max) {
+        cfg->buckets[THROTTLE_OPS_WRITE].max = arg->iops_write_max;
+    }
+
+    if (arg->has_bps_total_max_length) {
+        cfg->buckets[THROTTLE_BPS_TOTAL].burst_length = arg->bps_total_max_length;
+    }
+    if (arg->has_bps_read_max_length) {
+        cfg->buckets[THROTTLE_BPS_READ].burst_length = arg->bps_read_max_length;
+    }
+    if (arg->has_bps_write_max_length) {
+        cfg->buckets[THROTTLE_BPS_WRITE].burst_length = arg->bps_write_max_length;
+    }
+    if (arg->has_iops_total_max_length) {
+        cfg->buckets[THROTTLE_OPS_TOTAL].burst_length = arg->iops_total_max_length;
+    }
+    if (arg->has_iops_read_max_length) {
+        cfg->buckets[THROTTLE_OPS_READ].burst_length = arg->iops_read_max_length;
+    }
+    if (arg->has_iops_write_max_length) {
+        cfg->buckets[THROTTLE_OPS_WRITE].burst_length = arg->iops_write_max_length;
+    }
+
+    if (arg->has_iops_size) {
+        cfg->op_size = arg->iops_size;
+    }
+
+    throttle_is_valid(cfg, errp);
+}
+
+/* write the options of a ThrottleConfig to a ThrottleLimits
+ *
+ * @cfg:    the ThrottleConfig to read from
+ * @var:    the ThrottleLimits to write to
+ */
+void throttle_config_to_throttle_limits(ThrottleConfig *cfg, ThrottleLimits *var)
+{
+    var->bps_total               = cfg->buckets[THROTTLE_BPS_TOTAL].avg;
+    var->bps_read                = cfg->buckets[THROTTLE_BPS_READ].avg;
+    var->bps_write               = cfg->buckets[THROTTLE_BPS_WRITE].avg;
+    var->iops_total              = cfg->buckets[THROTTLE_OPS_TOTAL].avg;
+    var->iops_read               = cfg->buckets[THROTTLE_OPS_READ].avg;
+    var->iops_write              = cfg->buckets[THROTTLE_OPS_WRITE].avg;
+    var->bps_total_max           = cfg->buckets[THROTTLE_BPS_TOTAL].max;
+    var->bps_read_max            = cfg->buckets[THROTTLE_BPS_READ].max;
+    var->bps_write_max           = cfg->buckets[THROTTLE_BPS_WRITE].max;
+    var->iops_total_max          = cfg->buckets[THROTTLE_OPS_TOTAL].max;
+    var->iops_read_max           = cfg->buckets[THROTTLE_OPS_READ].max;
+    var->iops_write_max          = cfg->buckets[THROTTLE_OPS_WRITE].max;
+    var->bps_total_max_length    = cfg->buckets[THROTTLE_BPS_TOTAL].burst_length;
+    var->bps_read_max_length     = cfg->buckets[THROTTLE_BPS_READ].burst_length;
+    var->bps_write_max_length    = cfg->buckets[THROTTLE_BPS_WRITE].burst_length;
+    var->iops_total_max_length   = cfg->buckets[THROTTLE_OPS_TOTAL].burst_length;
+    var->iops_read_max_length    = cfg->buckets[THROTTLE_OPS_READ].burst_length;
+    var->iops_write_max_length   = cfg->buckets[THROTTLE_OPS_WRITE].burst_length;
+    var->iops_size               = cfg->op_size;
+
+    var->has_bps_total = true;
+    var->has_bps_read = true;
+    var->has_bps_write = true;
+    var->has_iops_total = true;
+    var->has_iops_read = true;
+    var->has_iops_write = true;
+    var->has_bps_total_max = true;
+    var->has_bps_read_max = true;
+    var->has_bps_write_max = true;
+    var->has_iops_total_max = true;
+    var->has_iops_read_max = true;
+    var->has_iops_write_max = true;
+    var->has_bps_read_max_length = true;
+    var->has_bps_total_max_length = true;
+    var->has_bps_write_max_length = true;
+    var->has_iops_total_max_length = true;
+    var->has_iops_read_max_length = true;
+    var->has_iops_write_max_length = true;
+    var->has_iops_size = true;
+}
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [Qemu-devel] [PATCH 5/7] block: add throttle block filter driver
  2017-07-14  9:45 [Qemu-devel] [PATCH 0/7] add throttle block driver filter Manos Pitsidianakis
                   ` (3 preceding siblings ...)
  2017-07-14  9:45 ` [Qemu-devel] [PATCH 4/7] block: convert ThrottleGroup to object with QOM Manos Pitsidianakis
@ 2017-07-14  9:45 ` Manos Pitsidianakis
  2017-07-18  9:21   ` [Qemu-devel] [Qemu-block] " Manos Pitsidianakis
  2017-07-14  9:45 ` [Qemu-devel] [PATCH 6/7] block: add BlockDevOptionsThrottle to QAPI Manos Pitsidianakis
  2017-07-14  9:45 ` [Qemu-devel] [PATCH 7/7] block: add throttle block filter driver interface tests Manos Pitsidianakis
  6 siblings, 1 reply; 17+ messages in thread
From: Manos Pitsidianakis @ 2017-07-14  9:45 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Stefan Hajnoczi, Alberto Garcia, qemu-block

block/throttle.c uses existing I/O throttle infrastructure inside a
block filter driver. I/O operations are intercepted in the filter's
read/write coroutines, and referred to block/throttle-groups.c

The driver can be used with the syntax
-drive driver=throttle,file.filename=foo.qcow2, \
        limits.iops-total=...,throttling-group=bar

The configuration flags and their semantics are identical to the
hardcoded throttling ones.

A node can be created referring to an existing group, and will overwrite
its limits if any are specified, otherwise they are retained.

Signed-off-by: Manos Pitsidianakis <el13635@mail.ntua.gr>
---
 block/Makefile.objs             |   1 +
 block/throttle.c                | 345 ++++++++++++++++++++++++++++++++++++++++
 include/qemu/throttle-options.h |   1 +
 3 files changed, 347 insertions(+)
 create mode 100644 block/throttle.c

diff --git a/block/Makefile.objs b/block/Makefile.objs
index 2aaede4ae1..6eaf78a046 100644
--- a/block/Makefile.objs
+++ b/block/Makefile.objs
@@ -25,6 +25,7 @@ block-obj-y += accounting.o dirty-bitmap.o
 block-obj-y += write-threshold.o
 block-obj-y += backup.o
 block-obj-$(CONFIG_REPLICATION) += replication.o
+block-obj-y += throttle.o
 
 block-obj-y += crypto.o
 
diff --git a/block/throttle.c b/block/throttle.c
new file mode 100644
index 0000000000..5665ccd8b5
--- /dev/null
+++ b/block/throttle.c
@@ -0,0 +1,345 @@
+/*
+ * QEMU block throttling filter driver infrastructure
+ *
+ * Copyright (c) 2017 Manos Pitsidianakis
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "block/throttle-groups.h"
+#include "qemu/throttle-options.h"
+#include "qapi/error.h"
+
+#undef THROTTLE_OPT_PREFIX
+#define THROTTLE_OPT_PREFIX "limits."
+static QemuOptsList throttle_opts = {
+    .name = "throttle",
+    .head = QTAILQ_HEAD_INITIALIZER(throttle_opts.head),
+    .desc = {
+        THROTTLE_OPTS,
+        {
+            .name = QEMU_OPT_THROTTLE_GROUP_NAME,
+            .type = QEMU_OPT_STRING,
+            .help = "throttle group name",
+        },
+        { /* end of list */ }
+    },
+};
+
+/* Extract ThrottleConfig options. Assumes cfg is initialized and will be
+ * checked for validity.
+ */
+static void throttle_extract_options(QemuOpts *opts, ThrottleConfig *cfg)
+{
+    if (qemu_opt_get(opts, THROTTLE_OPT_PREFIX QEMU_OPT_BPS_TOTAL)) {
+        cfg->buckets[THROTTLE_BPS_TOTAL].avg =
+            qemu_opt_get_number(opts, THROTTLE_OPT_PREFIX QEMU_OPT_BPS_TOTAL,
+                                0);
+    }
+    if (qemu_opt_get(opts, THROTTLE_OPT_PREFIX QEMU_OPT_BPS_READ)) {
+        cfg->buckets[THROTTLE_BPS_READ].avg  =
+            qemu_opt_get_number(opts, THROTTLE_OPT_PREFIX QEMU_OPT_BPS_READ,
+                                0);
+    }
+    if (qemu_opt_get(opts, THROTTLE_OPT_PREFIX QEMU_OPT_BPS_WRITE)) {
+        cfg->buckets[THROTTLE_BPS_WRITE].avg =
+            qemu_opt_get_number(opts, THROTTLE_OPT_PREFIX QEMU_OPT_BPS_WRITE,
+                                0);
+    }
+    if (qemu_opt_get(opts, THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_TOTAL)) {
+        cfg->buckets[THROTTLE_OPS_TOTAL].avg =
+            qemu_opt_get_number(opts, THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_TOTAL,
+                                0);
+    }
+    if (qemu_opt_get(opts, THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_READ)) {
+        cfg->buckets[THROTTLE_OPS_READ].avg =
+            qemu_opt_get_number(opts, THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_READ,
+                                0);
+    }
+    if (qemu_opt_get(opts, THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_WRITE)) {
+        cfg->buckets[THROTTLE_OPS_WRITE].avg =
+            qemu_opt_get_number(opts, THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_WRITE,
+                                0);
+    }
+    if (qemu_opt_get(opts, THROTTLE_OPT_PREFIX QEMU_OPT_BPS_TOTAL_MAX)) {
+        cfg->buckets[THROTTLE_BPS_TOTAL].max =
+            qemu_opt_get_number(opts, THROTTLE_OPT_PREFIX
+                                QEMU_OPT_BPS_TOTAL_MAX, 0);
+    }
+    if (qemu_opt_get(opts, THROTTLE_OPT_PREFIX QEMU_OPT_BPS_READ_MAX)) {
+        cfg->buckets[THROTTLE_BPS_READ].max  =
+            qemu_opt_get_number(opts, THROTTLE_OPT_PREFIX
+                                QEMU_OPT_BPS_READ_MAX, 0);
+    }
+    if (qemu_opt_get(opts, THROTTLE_OPT_PREFIX QEMU_OPT_BPS_WRITE_MAX)) {
+        cfg->buckets[THROTTLE_BPS_WRITE].max =
+            qemu_opt_get_number(opts, THROTTLE_OPT_PREFIX
+                                QEMU_OPT_BPS_WRITE_MAX, 0);
+    }
+    if (qemu_opt_get(opts, THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_TOTAL_MAX)) {
+        cfg->buckets[THROTTLE_OPS_TOTAL].max =
+            qemu_opt_get_number(opts, THROTTLE_OPT_PREFIX
+                                QEMU_OPT_IOPS_TOTAL_MAX, 0);
+    }
+    if (qemu_opt_get(opts, THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_READ_MAX)) {
+        cfg->buckets[THROTTLE_OPS_READ].max =
+            qemu_opt_get_number(opts, THROTTLE_OPT_PREFIX
+                                QEMU_OPT_IOPS_READ_MAX, 0);
+    }
+    if (qemu_opt_get(opts, THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_WRITE_MAX)) {
+        cfg->buckets[THROTTLE_OPS_WRITE].max =
+            qemu_opt_get_number(opts, THROTTLE_OPT_PREFIX
+                                QEMU_OPT_IOPS_WRITE_MAX, 0);
+    }
+    if (qemu_opt_get(opts, THROTTLE_OPT_PREFIX QEMU_OPT_BPS_TOTAL_MAX_LENGTH)) {
+        cfg->buckets[THROTTLE_BPS_TOTAL].burst_length =
+            qemu_opt_get_number(opts, THROTTLE_OPT_PREFIX
+                                QEMU_OPT_BPS_TOTAL_MAX_LENGTH, 1);
+    }
+    if (qemu_opt_get(opts, THROTTLE_OPT_PREFIX QEMU_OPT_BPS_READ_MAX_LENGTH)) {
+        cfg->buckets[THROTTLE_BPS_READ].burst_length  =
+            qemu_opt_get_number(opts, THROTTLE_OPT_PREFIX
+                                QEMU_OPT_BPS_READ_MAX_LENGTH, 1);
+    }
+    if (qemu_opt_get(opts, THROTTLE_OPT_PREFIX QEMU_OPT_BPS_WRITE_MAX_LENGTH)) {
+        cfg->buckets[THROTTLE_BPS_WRITE].burst_length =
+            qemu_opt_get_number(opts, THROTTLE_OPT_PREFIX
+                                QEMU_OPT_BPS_WRITE_MAX_LENGTH, 1);
+    }
+    if (qemu_opt_get(opts, THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_TOTAL_MAX_LENGTH)) {
+        cfg->buckets[THROTTLE_OPS_TOTAL].burst_length =
+            qemu_opt_get_number(opts, THROTTLE_OPT_PREFIX
+                                QEMU_OPT_IOPS_TOTAL_MAX_LENGTH, 1);
+    }
+    if (qemu_opt_get(opts, THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_READ_MAX_LENGTH)) {
+        cfg->buckets[THROTTLE_OPS_READ].burst_length =
+            qemu_opt_get_number(opts, THROTTLE_OPT_PREFIX
+                                QEMU_OPT_IOPS_READ_MAX_LENGTH, 1);
+    }
+    if (qemu_opt_get(opts, THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_WRITE_MAX_LENGTH)) {
+        cfg->buckets[THROTTLE_OPS_WRITE].burst_length =
+            qemu_opt_get_number(opts, THROTTLE_OPT_PREFIX
+                                QEMU_OPT_IOPS_WRITE_MAX_LENGTH, 1);
+    }
+    if (qemu_opt_get(opts, THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_SIZE)) {
+        cfg->op_size =
+            qemu_opt_get_number(opts, THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_SIZE,
+                                0);
+    }
+}
+
+static int throttle_configure_tgm(BlockDriverState *bs,
+                                  ThrottleGroupMember *tgm,
+                                  QDict *options, Error **errp)
+{
+    int ret;
+    ThrottleConfig cfg;
+    const char *group_name = NULL;
+    Error *local_err = NULL;
+    QemuOpts *opts = qemu_opts_create(&throttle_opts, NULL, 0, &local_err);
+    if (local_err) {
+        error_propagate(errp, local_err);
+        return -EINVAL;
+    }
+
+    qemu_opts_absorb_qdict(opts, options, &local_err);
+    if (local_err) {
+        error_propagate(errp, local_err);
+        goto err;
+    }
+
+    /* If no name is specified, an anonymous group will be created */
+    group_name = qemu_opt_get(opts, QEMU_OPT_THROTTLE_GROUP_NAME);
+
+    /* Register membership to group with name group_name */
+    throttle_group_register_tgm(tgm, group_name, bdrv_get_aio_context(bs));
+
+    /* Copy previous configuration */
+    throttle_group_get_config(tgm, &cfg);
+
+    /* Change limits if user has specified them */
+    throttle_extract_options(opts, &cfg);
+    if (!throttle_is_valid(&cfg, errp)) {
+        throttle_group_unregister_tgm(tgm);
+        goto err;
+    }
+    /* Update group configuration */
+    throttle_group_config(tgm, &cfg);
+
+    ret = 0;
+    goto fin;
+
+err:
+    ret = -EINVAL;
+fin:
+    qemu_opts_del(opts);
+    return ret;
+}
+
+static int throttle_open(BlockDriverState *bs, QDict *options,
+                            int flags, Error **errp)
+{
+    ThrottleGroupMember *tgm = bs->opaque;
+
+    bs->file = bdrv_open_child(NULL, options, "file",
+                                    bs, &child_file, false, errp);
+    if (!bs->file) {
+        return -EINVAL;
+    }
+
+    return throttle_configure_tgm(bs, tgm, options, errp);
+}
+
+static void throttle_close(BlockDriverState *bs)
+{
+    ThrottleGroupMember *tgm = bs->opaque;
+    throttle_group_unregister_tgm(tgm);
+}
+
+
+static int64_t throttle_getlength(BlockDriverState *bs)
+{
+    return bdrv_getlength(bs->file->bs);
+}
+
+
+static int coroutine_fn throttle_co_preadv(BlockDriverState *bs,
+                                           uint64_t offset, uint64_t bytes,
+                                           QEMUIOVector *qiov, int flags)
+{
+
+    ThrottleGroupMember *tgm = bs->opaque;
+    throttle_group_co_io_limits_intercept(tgm, bytes, false);
+
+    return bdrv_co_preadv(bs->file, offset, bytes, qiov, flags);
+}
+
+static int coroutine_fn throttle_co_pwritev(BlockDriverState *bs,
+                                            uint64_t offset, uint64_t bytes,
+                                            QEMUIOVector *qiov, int flags)
+{
+    ThrottleGroupMember *tgm = bs->opaque;
+    throttle_group_co_io_limits_intercept(tgm, bytes, true);
+
+    return bdrv_co_preadv(bs->file, offset, bytes, qiov, flags);
+}
+
+static int coroutine_fn throttle_co_pwrite_zeroes(BlockDriverState *bs,
+        int64_t offset, int bytes, BdrvRequestFlags flags)
+{
+    ThrottleGroupMember *tgm = bs->opaque;
+    throttle_group_co_io_limits_intercept(tgm, bytes, true);
+
+    return bdrv_co_pwrite_zeroes(bs->file, offset, bytes, flags);
+}
+
+static int coroutine_fn throttle_co_pdiscard(BlockDriverState *bs,
+        int64_t offset, int bytes)
+{
+    ThrottleGroupMember *tgm = bs->opaque;
+    throttle_group_co_io_limits_intercept(tgm, bytes, true);
+
+    return bdrv_co_pdiscard(bs->file->bs, offset, bytes);
+}
+
+static int throttle_co_flush(BlockDriverState *bs)
+{
+    return bdrv_co_flush(bs->file->bs);
+}
+
+static void throttle_detach_aio_context(BlockDriverState *bs)
+{
+    ThrottleGroupMember *tgm = bs->opaque;
+    throttle_group_detach_aio_context(tgm);
+}
+
+static void throttle_attach_aio_context(BlockDriverState *bs,
+                                    AioContext *new_context)
+{
+    ThrottleGroupMember *tgm = bs->opaque;
+    throttle_group_attach_aio_context(tgm, new_context);
+}
+
+static int throttle_reopen_prepare(BDRVReopenState *reopen_state,
+                                      BlockReopenQueue *queue, Error **errp)
+{
+    ThrottleGroupMember *tgm = NULL;
+
+    assert(reopen_state != NULL);
+    assert(reopen_state->bs != NULL);
+
+    reopen_state->opaque = g_new0(ThrottleGroupMember, 1);
+    tgm = reopen_state->opaque;
+
+    return throttle_configure_tgm(reopen_state->bs, tgm, reopen_state->options,
+            errp);
+}
+
+static void throttle_reopen_commit(BDRVReopenState *state)
+{
+    ThrottleGroupMember *tgm = state->bs->opaque;
+
+    throttle_group_unregister_tgm(tgm);
+    g_free(state->bs->opaque);
+    state->bs->opaque = state->opaque;
+    state->opaque = NULL;
+}
+
+static void throttle_reopen_abort(BDRVReopenState *state)
+{
+    ThrottleGroupMember *tgm = state->opaque;
+
+    throttle_group_unregister_tgm(tgm);
+    g_free(state->opaque);
+    state->opaque = NULL;
+}
+
+static BlockDriver bdrv_throttle = {
+    .format_name                        =   "throttle",
+    .protocol_name                      =   "throttle",
+    .instance_size                      =   sizeof(ThrottleGroupMember),
+
+    .bdrv_file_open                     =   throttle_open,
+    .bdrv_close                         =   throttle_close,
+    .bdrv_co_flush                      =   throttle_co_flush,
+
+    .bdrv_child_perm                    =   bdrv_filter_default_perms,
+
+    .bdrv_getlength                     =   throttle_getlength,
+
+    .bdrv_co_preadv                     =   throttle_co_preadv,
+    .bdrv_co_pwritev                    =   throttle_co_pwritev,
+
+    .bdrv_co_pwrite_zeroes              =   throttle_co_pwrite_zeroes,
+    .bdrv_co_pdiscard                   =   throttle_co_pdiscard,
+
+    .bdrv_recurse_is_first_non_filter   =   bdrv_recurse_is_first_non_filter,
+
+    .bdrv_attach_aio_context            =   throttle_attach_aio_context,
+    .bdrv_detach_aio_context            =   throttle_detach_aio_context,
+
+    .bdrv_reopen_prepare                =   throttle_reopen_prepare,
+    .bdrv_reopen_commit                 =   throttle_reopen_commit,
+    .bdrv_reopen_abort                  =   throttle_reopen_abort,
+
+    .is_filter                          =   true,
+};
+
+static void bdrv_throttle_init(void)
+{
+    bdrv_register(&bdrv_throttle);
+}
+
+block_init(bdrv_throttle_init);
diff --git a/include/qemu/throttle-options.h b/include/qemu/throttle-options.h
index 182b7896e1..508ee72625 100644
--- a/include/qemu/throttle-options.h
+++ b/include/qemu/throttle-options.h
@@ -29,6 +29,7 @@
 #define QEMU_OPT_BPS_WRITE_MAX "bps-write-max"
 #define QEMU_OPT_BPS_WRITE_MAX_LENGTH "bps-write-max-length"
 #define QEMU_OPT_IOPS_SIZE "iops-size"
+#define QEMU_OPT_THROTTLE_GROUP_NAME "throttling-group"
 
 #define THROTTLE_OPT_PREFIX "throttling."
 #define THROTTLE_OPTS \
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [Qemu-devel] [PATCH 6/7] block: add BlockDevOptionsThrottle to QAPI
  2017-07-14  9:45 [Qemu-devel] [PATCH 0/7] add throttle block driver filter Manos Pitsidianakis
                   ` (4 preceding siblings ...)
  2017-07-14  9:45 ` [Qemu-devel] [PATCH 5/7] block: add throttle block filter driver Manos Pitsidianakis
@ 2017-07-14  9:45 ` Manos Pitsidianakis
  2017-07-14  9:45 ` [Qemu-devel] [PATCH 7/7] block: add throttle block filter driver interface tests Manos Pitsidianakis
  6 siblings, 0 replies; 17+ messages in thread
From: Manos Pitsidianakis @ 2017-07-14  9:45 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Stefan Hajnoczi, Alberto Garcia, qemu-block

This is needed to configure throttle filter driver nodes with QAPI.

Signed-off-by: Manos Pitsidianakis <el13635@mail.ntua.gr>
---
 qapi/block-core.json | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)

diff --git a/qapi/block-core.json b/qapi/block-core.json
index 1084158b6a..9aaad97c54 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -2210,6 +2210,7 @@
 # Drivers that are supported in block device operations.
 #
 # @vxhs: Since 2.10
+# @throttle: Since 2.10
 #
 # Since: 2.9
 ##
@@ -2219,7 +2220,7 @@
             'host_device', 'http', 'https', 'iscsi', 'luks', 'nbd', 'nfs',
             'null-aio', 'null-co', 'parallels', 'qcow', 'qcow2', 'qed',
             'quorum', 'raw', 'rbd', 'replication', 'sheepdog', 'ssh',
-            'vdi', 'vhdx', 'vmdk', 'vpc', 'vvfat', 'vxhs' ] }
+            'throttle', 'vdi', 'vhdx', 'vmdk', 'vpc', 'vvfat', 'vxhs' ] }
 
 ##
 # @BlockdevOptionsFile:
@@ -3084,6 +3085,21 @@
             '*tls-creds': 'str' } }
 
 ##
+# @BlockdevOptionsThrottle:
+#
+# Driver specific block device options for the throttle driver
+#
+# @throttling-group: the name of the throttling group to use
+# @file:             reference to or definition of the data source block device
+# @limits:           ThrottleLimits options
+# Since: 2.10
+##
+{ 'struct': 'BlockdevOptionsThrottle',
+  'data': { '*throttling-group': 'str',
+            'file' : 'BlockdevRef',
+            '*limits' : 'ThrottleLimits'
+             } }
+##
 # @BlockdevOptions:
 #
 # Options for creating a block device.  Many options are available for all
@@ -3144,6 +3160,7 @@
       'replication':'BlockdevOptionsReplication',
       'sheepdog':   'BlockdevOptionsSheepdog',
       'ssh':        'BlockdevOptionsSsh',
+      'throttle':   'BlockdevOptionsThrottle',
       'vdi':        'BlockdevOptionsGenericFormat',
       'vhdx':       'BlockdevOptionsGenericFormat',
       'vmdk':       'BlockdevOptionsGenericCOWFormat',
@@ -3883,3 +3900,4 @@
   'data' : { 'parent': 'str',
              '*child': 'str',
              '*node': 'str' } }
+
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [Qemu-devel] [PATCH 7/7] block: add throttle block filter driver interface tests
  2017-07-14  9:45 [Qemu-devel] [PATCH 0/7] add throttle block driver filter Manos Pitsidianakis
                   ` (5 preceding siblings ...)
  2017-07-14  9:45 ` [Qemu-devel] [PATCH 6/7] block: add BlockDevOptionsThrottle to QAPI Manos Pitsidianakis
@ 2017-07-14  9:45 ` Manos Pitsidianakis
  6 siblings, 0 replies; 17+ messages in thread
From: Manos Pitsidianakis @ 2017-07-14  9:45 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, Stefan Hajnoczi, Alberto Garcia, qemu-block

Signed-off-by: Manos Pitsidianakis <el13635@mail.ntua.gr>
---
 tests/qemu-iotests/184     | 237 +++++++++++++++++++++++++++++++++
 tests/qemu-iotests/184.out | 319 +++++++++++++++++++++++++++++++++++++++++++++
 tests/qemu-iotests/group   |   1 +
 3 files changed, 557 insertions(+)
 create mode 100755 tests/qemu-iotests/184
 create mode 100644 tests/qemu-iotests/184.out

diff --git a/tests/qemu-iotests/184 b/tests/qemu-iotests/184
new file mode 100755
index 0000000000..05f724a274
--- /dev/null
+++ b/tests/qemu-iotests/184
@@ -0,0 +1,237 @@
+#!/bin/bash
+#
+# Test I/O throttle block filter driver interface
+#
+# Copyright (C) 2017 Manos Pitsidianakis
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# creator
+owner=Manos Pitsidianakis
+
+seq=`basename $0`
+echo "QA output created by $seq"
+
+here=`pwd`
+status=1	# failure is the default!
+
+_cleanup()
+{
+    _cleanup_test_img
+}
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+# get standard environment, filters and checks
+. ./common.rc
+. ./common.filter
+
+_supported_fmt raw qcow2
+_supported_proto file
+_supported_os Linux
+
+function do_run_qemu()
+{
+    echo Testing: "$@" | _filter_imgfmt
+    $QEMU -nographic -qmp-pretty stdio -serial none "$@"
+    echo
+}
+
+function run_qemu()
+{
+    do_run_qemu "$@" 2>&1 | _filter_testdir | _filter_qemu | _filter_qmp\
+                          | _filter_qemu_io | _filter_generated_node_ids
+}
+
+_make_test_img 64M
+test_throttle=$($QEMU_IMG --help|grep throttle)
+[ "$test_throttle" = "" ] && _supported_fmt throttle
+
+echo
+echo "== checking interface =="
+
+run_qemu <<EOF
+{ "execute": "qmp_capabilities" }
+{ "execute": "blockdev-add",
+  "arguments": {
+    "driver": "$IMGFMT",
+    "node-name": "disk0",
+    "file": {
+      "driver": "file",
+      "filename": "$TEST_IMG"
+    }
+  }
+}
+{ "execute": "object-add",
+  "arguments": {
+    "qom-type": "throttling-group",
+    "id": "group0",
+    "props": {
+      "limits" : {
+        "iops-total": 1000
+      }
+    }
+  }
+}
+{ "execute": "blockdev-add",
+  "arguments": {
+    "driver": "throttle",
+    "node-name": "throttle0",
+    "throttling-group": "group0",
+    "file": "disk0"
+  }
+}
+{ "execute": "query-named-block-nodes" }
+{ "execute": "query-block" }
+{ "execute": "quit" }
+EOF
+
+echo
+echo "== create anonymous group =="
+
+run_qemu <<EOF
+{ "execute": "qmp_capabilities" }
+{ "execute": "blockdev-add",
+  "arguments": {
+    "driver": "$IMGFMT",
+    "node-name": "disk0",
+    "file": {
+      "driver": "file",
+      "filename": "$TEST_IMG"
+    }
+  }
+}
+{ "execute": "blockdev-add",
+  "arguments": {
+    "driver": "throttle",
+    "node-name": "throttle-anonymous",
+    "limits" : {
+      "iops-total" : 1000
+    },
+    "file": "disk0"
+  }
+}
+{ "execute": "quit" }
+EOF
+
+echo
+echo "== property changes in ThrottleGroup =="
+
+run_qemu <<EOF
+{ "execute": "qmp_capabilities" }
+{ "execute": "object-add",
+  "arguments": {
+    "qom-type": "throttling-group",
+    "id": "group0",
+    "props" : {
+      "limits": {
+          "iops-total": 1000
+      }
+    }
+  }
+}
+{ "execute" : "qom-get",
+  "arguments" : {
+    "path" : "group0",
+    "property" : "limits"
+  }
+}
+{ "execute" : "qom-set",
+    "arguments" : {
+        "path" : "group0",
+        "property" : "limits",
+        "value" : {
+            "iops-total" : 0
+        }
+    }
+}
+{ "execute" : "qom-get",
+  "arguments" : {
+    "path" : "group0",
+    "property" : "limits"
+  }
+}
+{ "execute": "blockdev-add",
+  "arguments": {
+    "driver": "$IMGFMT",
+    "node-name": "disk0",
+    "file": {
+      "driver": "file",
+      "filename": "$TEST_IMG"
+    }
+  }
+}
+{ "execute": "blockdev-add",
+  "arguments": {
+    "driver": "throttle",
+    "node-name": "throttle0",
+    "throttling-group": "group0",
+    "limits" : {
+      "iops-total" : 128,
+      "iops-total-max" : 256,
+      "iops-total-max-length" : 60
+    },
+    "file": "disk0"
+  }
+}
+{ "execute" : "qom-get",
+  "arguments" : {
+    "path" : "group0",
+    "property" : "limits"
+  }
+}
+{ "execute": "quit" }
+EOF
+
+echo
+echo "== object creation/set errors  =="
+
+run_qemu <<EOF
+{ "execute": "qmp_capabilities" }
+{ "execute": "object-add",
+  "arguments": {
+    "qom-type": "throttling-group",
+    "id": "group0",
+    "props" : {
+      "limits": {
+          "iops-total": 1000
+      }
+    }
+  }
+}
+{ "execute" : "qom-set",
+  "arguments" : {
+    "path" : "group0",
+    "property" : "x-iops-total",
+    "value" : 0
+  }
+}
+{ "execute" : "qom-set",
+    "arguments" : {
+        "path" : "group0",
+        "property" : "limits",
+        "value" : {
+            "iops-total" : 10,
+            "iops-read" : 10
+        }
+    }
+}
+{ "execute": "quit" }
+EOF
+
+echo
+# success, all done
+echo "*** done"
+rm -f $seq.full
+status=0
diff --git a/tests/qemu-iotests/184.out b/tests/qemu-iotests/184.out
new file mode 100644
index 0000000000..35d2683860
--- /dev/null
+++ b/tests/qemu-iotests/184.out
@@ -0,0 +1,319 @@
+QA output created by 184
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864
+
+== checking interface ==
+Testing:
+{
+    QMP_VERSION
+}
+{
+    "return": {
+    }
+}
+{
+    "return": {
+    }
+}
+{
+    "return": {
+    }
+}
+{
+    "return": {
+    }
+}
+{
+    "return": [
+        {
+            "iops_rd": 0,
+            "detect_zeroes": "off",
+            "image": {
+                "virtual-size": 67108864,
+                "filename": "json:{\"throttling-group\": \"group0\", \"driver\": \"throttle\", \"file\": {\"driver\": \"raw\", \"file\": {\"driver\": \"file\", \"filename\": \"TEST_DIR/t.raw\"}}}",
+                "format": "throttle",
+                "actual-size": 0
+            },
+            "iops_wr": 0,
+            "ro": false,
+            "node-name": "throttle0",
+            "backing_file_depth": 0,
+            "drv": "throttle",
+            "iops": 0,
+            "bps_wr": 0,
+            "write_threshold": 0,
+            "encrypted": false,
+            "bps": 0,
+            "bps_rd": 0,
+            "cache": {
+                "no-flush": false,
+                "direct": false,
+                "writeback": true
+            },
+            "file": "json:{\"throttling-group\": \"group0\", \"driver\": \"throttle\", \"file\": {\"driver\": \"raw\", \"file\": {\"driver\": \"file\", \"filename\": \"TEST_DIR/t.raw\"}}}",
+            "encryption_key_missing": false
+        },
+        {
+            "iops_rd": 0,
+            "detect_zeroes": "off",
+            "image": {
+                "virtual-size": 67108864,
+                "filename": "TEST_DIR/t.raw",
+                "format": "raw",
+                "actual-size": 0,
+                "dirty-flag": false
+            },
+            "iops_wr": 0,
+            "ro": false,
+            "node-name": "disk0",
+            "backing_file_depth": 0,
+            "drv": "raw",
+            "iops": 0,
+            "bps_wr": 0,
+            "write_threshold": 0,
+            "encrypted": false,
+            "bps": 0,
+            "bps_rd": 0,
+            "cache": {
+                "no-flush": false,
+                "direct": false,
+                "writeback": true
+            },
+            "file": "TEST_DIR/t.raw",
+            "encryption_key_missing": false
+        },
+        {
+            "iops_rd": 0,
+            "detect_zeroes": "off",
+            "image": {
+                "virtual-size": 67108864,
+                "filename": "TEST_DIR/t.raw",
+                "format": "file",
+                "actual-size": 0,
+                "dirty-flag": false
+            },
+            "iops_wr": 0,
+            "ro": false,
+            "node-name": "NODE_NAME",
+            "backing_file_depth": 0,
+            "drv": "file",
+            "iops": 0,
+            "bps_wr": 0,
+            "write_threshold": 0,
+            "encrypted": false,
+            "bps": 0,
+            "bps_rd": 0,
+            "cache": {
+                "no-flush": false,
+                "direct": false,
+                "writeback": true
+            },
+            "file": "TEST_DIR/t.raw",
+            "encryption_key_missing": false
+        }
+    ]
+}
+{
+    "return": [
+    ]
+}
+{
+    "return": {
+    }
+}
+{
+    "timestamp": {
+        "seconds":  TIMESTAMP,
+        "microseconds":  TIMESTAMP
+    },
+    "event": "SHUTDOWN",
+    "data": {
+        "guest": false
+    }
+}
+
+
+== create anonymous group ==
+Testing:
+{
+    QMP_VERSION
+}
+{
+    "return": {
+    }
+}
+{
+    "return": {
+    }
+}
+{
+    "return": {
+    }
+}
+{
+    "return": {
+    }
+}
+{
+    "timestamp": {
+        "seconds":  TIMESTAMP,
+        "microseconds":  TIMESTAMP
+    },
+    "event": "SHUTDOWN",
+    "data": {
+        "guest": false
+    }
+}
+
+
+== property changes in ThrottleGroup ==
+Testing:
+{
+    QMP_VERSION
+}
+{
+    "return": {
+    }
+}
+{
+    "return": {
+    }
+}
+{
+    "return": {
+        "bps-read-max-length": 1,
+        "iops-read-max-length": 1,
+        "bps-read-max": 0,
+        "bps-total": 0,
+        "iops-total-max-length": 1,
+        "iops-total": 1000,
+        "iops-write-max": 0,
+        "bps-write": 0,
+        "bps-total-max": 0,
+        "bps-write-max": 0,
+        "iops-size": 0,
+        "iops-read": 0,
+        "iops-write-max-length": 1,
+        "iops-write": 0,
+        "bps-total-max-length": 1,
+        "iops-read-max": 0,
+        "bps-read": 0,
+        "bps-write-max-length": 1,
+        "iops-total-max": 0
+    }
+}
+{
+    "return": {
+    }
+}
+{
+    "return": {
+        "bps-read-max-length": 1,
+        "iops-read-max-length": 1,
+        "bps-read-max": 0,
+        "bps-total": 0,
+        "iops-total-max-length": 1,
+        "iops-total": 0,
+        "iops-write-max": 0,
+        "bps-write": 0,
+        "bps-total-max": 0,
+        "bps-write-max": 0,
+        "iops-size": 0,
+        "iops-read": 0,
+        "iops-write-max-length": 1,
+        "iops-write": 0,
+        "bps-total-max-length": 1,
+        "iops-read-max": 0,
+        "bps-read": 0,
+        "bps-write-max-length": 1,
+        "iops-total-max": 0
+    }
+}
+{
+    "return": {
+    }
+}
+{
+    "return": {
+    }
+}
+{
+    "return": {
+        "bps-read-max-length": 1,
+        "iops-read-max-length": 1,
+        "bps-read-max": 0,
+        "bps-total": 0,
+        "iops-total-max-length": 60,
+        "iops-total": 128,
+        "iops-write-max": 0,
+        "bps-write": 0,
+        "bps-total-max": 0,
+        "bps-write-max": 0,
+        "iops-size": 0,
+        "iops-read": 0,
+        "iops-write-max-length": 1,
+        "iops-write": 0,
+        "bps-total-max-length": 1,
+        "iops-read-max": 0,
+        "bps-read": 0,
+        "bps-write-max-length": 1,
+        "iops-total-max": 256
+    }
+}
+{
+    "return": {
+    }
+}
+{
+    "timestamp": {
+        "seconds":  TIMESTAMP,
+        "microseconds":  TIMESTAMP
+    },
+    "event": "SHUTDOWN",
+    "data": {
+        "guest": false
+    }
+}
+
+
+== object creation/set errors  ==
+Testing:
+{
+    QMP_VERSION
+}
+{
+    "return": {
+    }
+}
+{
+    "return": {
+    }
+}
+{
+    "error": {
+        "class": "GenericError",
+        "desc": "Property cannot be set after initialization"
+    }
+}
+{
+    "error": {
+        "class": "GenericError",
+        "desc": "bps/iops/max total values and read/write values cannot be used at the same time"
+    }
+}
+{
+    "return": {
+    }
+}
+{
+    "timestamp": {
+        "seconds":  TIMESTAMP,
+        "microseconds":  TIMESTAMP
+    },
+    "event": "SHUTDOWN",
+    "data": {
+        "guest": false
+    }
+}
+
+
+*** done
diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group
index 2aba585287..88f32aeddf 100644
--- a/tests/qemu-iotests/group
+++ b/tests/qemu-iotests/group
@@ -180,6 +180,7 @@
 181 rw auto migration
 182 rw auto quick
 183 rw auto migration
+184 rw auto quick
 185 rw auto
 188 rw auto quick
 189 rw auto quick
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* Re: [Qemu-devel] [Qemu-block] [PATCH 5/7] block: add throttle block filter driver
  2017-07-14  9:45 ` [Qemu-devel] [PATCH 5/7] block: add throttle block filter driver Manos Pitsidianakis
@ 2017-07-18  9:21   ` Manos Pitsidianakis
  0 siblings, 0 replies; 17+ messages in thread
From: Manos Pitsidianakis @ 2017-07-18  9:21 UTC (permalink / raw)
  To: qemu-devel; +Cc: Kevin Wolf, qemu-block, Stefan Hajnoczi, Alberto Garcia

[-- Attachment #1: Type: text/plain, Size: 2233 bytes --]

On Fri, Jul 14, 2017 at 12:45:19PM +0300, Manos Pitsidianakis wrote:
>+static BlockDriver bdrv_throttle = {
>+    .format_name                        =   "throttle",
>+    .protocol_name                      =   "throttle",
>+    .instance_size                      =   sizeof(ThrottleGroupMember),
>+
>+    .bdrv_file_open                     =   throttle_open,
>+    .bdrv_close                         =   throttle_close,
>+    .bdrv_co_flush                      =   throttle_co_flush,
>+
>+    .bdrv_child_perm                    =   bdrv_filter_default_perms,
>+
>+    .bdrv_getlength                     =   throttle_getlength,
>+
>+    .bdrv_co_preadv                     =   throttle_co_preadv,
>+    .bdrv_co_pwritev                    =   throttle_co_pwritev,
>+
>+    .bdrv_co_pwrite_zeroes              =   throttle_co_pwrite_zeroes,
>+    .bdrv_co_pdiscard                   =   throttle_co_pdiscard,
>+
>+    .bdrv_recurse_is_first_non_filter   =   bdrv_recurse_is_first_non_filter,

This is wrong! There should be a separate function to pass 
bdrv_recurse_is_first_non_filter to bs->file->bs.


>+    .bdrv_attach_aio_context            =   throttle_attach_aio_context,
>+    .bdrv_detach_aio_context            =   throttle_detach_aio_context,
>+
>+    .bdrv_reopen_prepare                =   throttle_reopen_prepare,
>+    .bdrv_reopen_commit                 =   throttle_reopen_commit,
>+    .bdrv_reopen_abort                  =   throttle_reopen_abort,
>+
>+    .is_filter                          =   true,
>+};
>+
>+static void bdrv_throttle_init(void)
>+{
>+    bdrv_register(&bdrv_throttle);
>+}
>+
>+block_init(bdrv_throttle_init);
>diff --git a/include/qemu/throttle-options.h b/include/qemu/throttle-options.h
>index 182b7896e1..508ee72625 100644
>--- a/include/qemu/throttle-options.h
>+++ b/include/qemu/throttle-options.h
>@@ -29,6 +29,7 @@
> #define QEMU_OPT_BPS_WRITE_MAX "bps-write-max"
> #define QEMU_OPT_BPS_WRITE_MAX_LENGTH "bps-write-max-length"
> #define QEMU_OPT_IOPS_SIZE "iops-size"
>+#define QEMU_OPT_THROTTLE_GROUP_NAME "throttling-group"
>
> #define THROTTLE_OPT_PREFIX "throttling."
> #define THROTTLE_OPTS \
>-- 
>2.11.0
>
>
>

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [Qemu-devel] [PATCH 1/7] block: move ThrottleGroup membership to ThrottleGroupMember
  2017-07-14  9:45 ` [Qemu-devel] [PATCH 1/7] block: move ThrottleGroup membership to ThrottleGroupMember Manos Pitsidianakis
@ 2017-07-24 14:36   ` Stefan Hajnoczi
  0 siblings, 0 replies; 17+ messages in thread
From: Stefan Hajnoczi @ 2017-07-24 14:36 UTC (permalink / raw)
  To: Manos Pitsidianakis; +Cc: qemu-devel, Kevin Wolf, Alberto Garcia, qemu-block

[-- Attachment #1: Type: text/plain, Size: 1259 bytes --]

On Fri, Jul 14, 2017 at 12:45:15PM +0300, Manos Pitsidianakis wrote:
> This commit eliminates the 1:1 relationship between BlockBackend and
> throttle group state.  Users will be able to create multiple throttle
> nodes, each with its own throttle group state, in the future.  The
> throttle group state cannot be per-BlockBackend anymore, it must be
> per-throttle node. This is done by gathering ThrottleGroup membership
> details from BlockBackendPublic into ThrottleGroupMember and refactoring
> existing code to use the structure.
> 
> Signed-off-by: Manos Pitsidianakis <el13635@mail.ntua.gr>
> ---
> 
> Notes:
>     Dropped R-b because I changed some of the comments (removed all BlockBackend
>         references in block/throttle-groups.c comments)
> 
>  block/block-backend.c           |  66 +++++----
>  block/qapi.c                    |   8 +-
>  block/throttle-groups.c         | 288 ++++++++++++++++++++--------------------
>  blockdev.c                      |   4 +-
>  include/block/throttle-groups.h |  39 +++++-
>  include/sysemu/block-backend.h  |  20 +--
>  tests/test-throttle.c           |  53 ++++----
>  7 files changed, 252 insertions(+), 226 deletions(-)

Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 455 bytes --]

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [Qemu-devel] [PATCH 2/7] block: add aio_context field in ThrottleGroupMember
  2017-07-14  9:45 ` [Qemu-devel] [PATCH 2/7] block: add aio_context field in ThrottleGroupMember Manos Pitsidianakis
@ 2017-07-24 14:38   ` Stefan Hajnoczi
  0 siblings, 0 replies; 17+ messages in thread
From: Stefan Hajnoczi @ 2017-07-24 14:38 UTC (permalink / raw)
  To: Manos Pitsidianakis; +Cc: qemu-devel, Kevin Wolf, Alberto Garcia, qemu-block

[-- Attachment #1: Type: text/plain, Size: 757 bytes --]

On Fri, Jul 14, 2017 at 12:45:16PM +0300, Manos Pitsidianakis wrote:
> timer_cb() needs to know about the current Aio context of the throttle
> request that is woken up. In order to make ThrottleGroupMember backend
> agnostic, this information is stored in an aio_context field instead of
> accessing it from BlockBackend.
> 
> Signed-off-by: Manos Pitsidianakis <el13635@mail.ntua.gr>
> ---
>  block/block-backend.c           | 14 ++++-----
>  block/throttle-groups.c         | 38 ++++++++++++++++---------
>  include/block/throttle-groups.h |  7 ++++-
>  tests/test-throttle.c           | 63 +++++++++++++++++++++--------------------
>  4 files changed, 69 insertions(+), 53 deletions(-)

Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 455 bytes --]

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [Qemu-devel] [PATCH 3/7] block: tidy ThrottleGroupMember initializations
  2017-07-14  9:45 ` [Qemu-devel] [PATCH 3/7] block: tidy ThrottleGroupMember initializations Manos Pitsidianakis
@ 2017-07-24 14:39   ` Stefan Hajnoczi
  0 siblings, 0 replies; 17+ messages in thread
From: Stefan Hajnoczi @ 2017-07-24 14:39 UTC (permalink / raw)
  To: Manos Pitsidianakis; +Cc: qemu-devel, Kevin Wolf, Alberto Garcia, qemu-block

[-- Attachment #1: Type: text/plain, Size: 506 bytes --]

On Fri, Jul 14, 2017 at 12:45:17PM +0300, Manos Pitsidianakis wrote:
> Move the CoMutex and CoQueue inits inside throttle_group_register_tgm()
> which is called whenever a ThrottleGroupMember is initialized. There's
> no need for them to be separate.
> 
> Signed-off-by: Manos Pitsidianakis <el13635@mail.ntua.gr>
> ---
>  block/block-backend.c   | 3 ---
>  block/throttle-groups.c | 3 +++
>  2 files changed, 3 insertions(+), 3 deletions(-)

Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 455 bytes --]

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [Qemu-devel] [PATCH 4/7] block: convert ThrottleGroup to object with QOM
  2017-07-14  9:45 ` [Qemu-devel] [PATCH 4/7] block: convert ThrottleGroup to object with QOM Manos Pitsidianakis
@ 2017-07-24 15:12   ` Stefan Hajnoczi
  2017-07-25 10:29     ` Manos Pitsidianakis
  0 siblings, 1 reply; 17+ messages in thread
From: Stefan Hajnoczi @ 2017-07-24 15:12 UTC (permalink / raw)
  To: Manos Pitsidianakis; +Cc: qemu-devel, Kevin Wolf, Alberto Garcia, qemu-block

[-- Attachment #1: Type: text/plain, Size: 42896 bytes --]

On Fri, Jul 14, 2017 at 12:45:18PM +0300, Manos Pitsidianakis wrote:
> ThrottleGroup is converted to an object. This will allow the future
> throttle block filter drive easy creation and configuration of throttle
> groups in QMP and cli.
> 
> A new QAPI struct, ThrottleLimits, is introduced to provide a shared
> struct for all throttle configuration needs in QMP.
> 
> ThrottleGroups can be created via CLI as
>     -object throttling-group,id=foo,x-iops-total=100,x-..

Please make the QOM name and struct name consistent.  Either
ThrottleGroup/throttle-group or ThrottlingGroup/throttling-group but not
ThrottleGroup/throttling-group.

> where x-* are individual limit properties. Since we can't add non-scalar
> properties in -object this interface must be used instead. However,
> setting these properties must be disabled after initialization because
> certain combinations of limits are forbidden and thus configuration
> changes should be done in one transaction. The individual properties
> will go away when support for non-scalar values in CLI is implemented
> and thus are marked as experimental.
> 
> ThrottleGroup also has a `limits` property that uses the ThrottleLimits
> struct.  It can be used to create ThrottleGroups or set the
> configuration in existing groups as follows:
> 
> { "execute": "object-add",
>   "arguments": {
>     "qom-type": "throttling-group",
>     "id": "foo",
>     "props" : {
>       "limits": {
>           "iops-total": 100
>       }
>     }
>   }
> }
> { "execute" : "qom-set",
>     "arguments" : {
>         "path" : "foo",
>         "property" : "limits",
>         "value" : {
>             "iops-total" : 99
>         }
>     }
> }
> 
> This also means a group's configuration can be fetched with qom-get.
> 
> ThrottleGroups can be anonymous which means they can't get accessed by
> other users ie they will always be units instead of group (Because they
> have one ThrottleGroupMember).
> 
> Signed-off-by: Manos Pitsidianakis <el13635@mail.ntua.gr>
> ---
> 
> Notes:
>     Note: I tested Markus Armbruster's patch in <87wp7fghi9.fsf@dusky.pond.sub.org>
>     on master and I can use this syntax successfuly:
>     -object '{ "qom-type" : "throttling-group", "id" : "foo", "props" : { "limits" \
>     : { "iops-total" : 1000 } } }'
>     If this gets merged using -object will be a little more verbose but at least we
>     won't have seperate properties, which is a good thing, so the x-* should be
>     dropped.
> 
>  block/throttle-groups.c         | 507 +++++++++++++++++++++++++++++++++++++---
>  include/block/throttle-groups.h |   3 +
>  include/qemu/throttle-options.h |  59 +++--
>  include/qemu/throttle.h         |   3 +
>  qapi/block-core.json            |  45 ++++
>  tests/test-throttle.c           |   1 +
>  util/throttle.c                 | 121 ++++++++++
>  7 files changed, 689 insertions(+), 50 deletions(-)
> 
> diff --git a/block/throttle-groups.c b/block/throttle-groups.c
> index f711a3dc62..4d1f82ec06 100644
> --- a/block/throttle-groups.c
> +++ b/block/throttle-groups.c
> @@ -25,9 +25,17 @@
>  #include "qemu/osdep.h"
>  #include "sysemu/block-backend.h"
>  #include "block/throttle-groups.h"
> +#include "qemu/throttle-options.h"
>  #include "qemu/queue.h"
>  #include "qemu/thread.h"
>  #include "sysemu/qtest.h"
> +#include "qapi/error.h"
> +#include "qapi-visit.h"
> +#include "qom/object.h"
> +#include "qom/object_interfaces.h"
> +
> +static void throttle_group_obj_init(Object *obj);
> +static void throttle_group_obj_complete(UserCreatable *obj, Error **errp);
>  
>  /* The ThrottleGroup structure (with its ThrottleState) is shared
>   * among different ThrottleGroupMembers and it's independent from
> @@ -54,6 +62,10 @@
>   * that ThrottleGroupMember has throttled requests in the queue.
>   */
>  typedef struct ThrottleGroup {
> +    Object parent_obj;
> +
> +    /* refuse individual property change if initialization is complete */
> +    bool is_initialized;
>      char *name; /* This is constant during the lifetime of the group */
>  
>      QemuMutex lock; /* This lock protects the following four fields */
> @@ -63,8 +75,7 @@ typedef struct ThrottleGroup {
>      bool any_timer_armed[2];
>      QEMUClockType clock_type;
>  
> -    /* These two are protected by the global throttle_groups_lock */
> -    unsigned refcount;
> +    /* This is protected by the global throttle_groups_lock */
>      QTAILQ_ENTRY(ThrottleGroup) list;
>  } ThrottleGroup;
>  
> @@ -77,7 +88,8 @@ static QTAILQ_HEAD(, ThrottleGroup) throttle_groups =
>   * If no ThrottleGroup is found with the given name a new one is
>   * created.
>   *
> - * @name: the name of the ThrottleGroup
> + * @name: the name of the ThrottleGroup, NULL means a new anonymous group will
> + *        be created.
>   * @ret:  the ThrottleState member of the ThrottleGroup
>   */
>  ThrottleState *throttle_group_incref(const char *name)
> @@ -87,32 +99,30 @@ ThrottleState *throttle_group_incref(const char *name)
>  
>      qemu_mutex_lock(&throttle_groups_lock);
>  
> -    /* Look for an existing group with that name */
> -    QTAILQ_FOREACH(iter, &throttle_groups, list) {
> -        if (!strcmp(name, iter->name)) {
> -            tg = iter;
> -            break;
> +    if (name) {
> +        /* Look for an existing group with that name */
> +        QTAILQ_FOREACH(iter, &throttle_groups, list) {
> +            if (!g_strcmp0(name, iter->name)) {
> +                tg = iter;
> +                break;
> +            }
>          }
>      }
>  
>      /* Create a new one if not found */
>      if (!tg) {
> -        tg = g_new0(ThrottleGroup, 1);
> +        /* new ThrottleGroup obj will have a refcnt = 1 */
> +        tg = THROTTLE_GROUP(object_new(TYPE_THROTTLE_GROUP));
>          tg->name = g_strdup(name);
> -        tg->clock_type = QEMU_CLOCK_REALTIME;
> -
> -        if (qtest_enabled()) {
> -            /* For testing block IO throttling only */
> -            tg->clock_type = QEMU_CLOCK_VIRTUAL;
> -        }
> -        qemu_mutex_init(&tg->lock);
> -        throttle_init(&tg->ts);
> -        QLIST_INIT(&tg->head);
> -
> -        QTAILQ_INSERT_TAIL(&throttle_groups, tg, list);
> +        throttle_group_obj_complete((UserCreatable *)tg, &error_abort);
>      }
>  
> -    tg->refcount++;
> +    qemu_mutex_lock(&tg->lock);
> +    if (!QLIST_EMPTY(&tg->head)) {
> +        /* only ref if the group is not empty */
> +        object_ref(OBJECT(tg));
> +    }
> +    qemu_mutex_unlock(&tg->lock);
>  
>      qemu_mutex_unlock(&throttle_groups_lock);
>  
> @@ -129,15 +139,7 @@ ThrottleState *throttle_group_incref(const char *name)
>  void throttle_group_unref(ThrottleState *ts)
>  {
>      ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts);
> -
> -    qemu_mutex_lock(&throttle_groups_lock);
> -    if (--tg->refcount == 0) {
> -        QTAILQ_REMOVE(&throttle_groups, tg, list);
> -        qemu_mutex_destroy(&tg->lock);
> -        g_free(tg->name);
> -        g_free(tg);
> -    }
> -    qemu_mutex_unlock(&throttle_groups_lock);
> +    object_unref(OBJECT(tg));
>  }
>  
>  /* Get the name from a ThrottleGroupMember's group. The name (and the pointer)
> @@ -572,9 +574,452 @@ void throttle_group_detach_aio_context(ThrottleGroupMember *tgm)
>      throttle_timers_detach_aio_context(tt);
>  }
>  
> +#define DOUBLE 0
> +#define UINT64 1
> +#define UNSIGNED 2
> +
> +typedef struct {
> +    BucketType type;
> +    int data_type;
> +    ptrdiff_t offset; /* offset in LeakyBucket struct. */
> +} ThrottleParamInfo;
> +
> +static ThrottleParamInfo throttle_iops_total_info = {

These can be made const.  This offers a tiny security benefit because
.offset will be read-only and therefore cannot be used to overwrite
arbitrary memory.  Exploits often gain control of execution by first
overwriting a function pointer or pointer field that they can influence.

Also, how about defining an array and looping over it to avoid
repetition?

  for (i = 0; i < ARRAY_SIZE(params); i++) {
      object_class_property_add(klass,
                                params[i].name,
                                "int",
                                throttle_group_get,
                                throttle_group_set,
                                NULL, &params[i],
                                &error_abort);
  }

> +    THROTTLE_OPS_TOTAL, DOUBLE, offsetof(LeakyBucket, avg),
> +};
> +
> +static ThrottleParamInfo throttle_iops_total_max_info = {
> +    THROTTLE_OPS_TOTAL, DOUBLE, offsetof(LeakyBucket, max),
> +};
> +
> +static ThrottleParamInfo throttle_iops_total_max_length_info = {
> +    THROTTLE_OPS_TOTAL, UNSIGNED, offsetof(LeakyBucket, burst_length),
> +};
> +
> +static ThrottleParamInfo throttle_iops_read_info = {
> +    THROTTLE_OPS_READ, DOUBLE, offsetof(LeakyBucket, avg),
> +};
> +
> +static ThrottleParamInfo throttle_iops_read_max_info = {
> +    THROTTLE_OPS_READ, DOUBLE, offsetof(LeakyBucket, max),
> +};
> +
> +static ThrottleParamInfo throttle_iops_read_max_length_info = {
> +    THROTTLE_OPS_READ, UNSIGNED, offsetof(LeakyBucket, burst_length),
> +};
> +
> +static ThrottleParamInfo throttle_iops_write_info = {
> +    THROTTLE_OPS_WRITE, DOUBLE, offsetof(LeakyBucket, avg),
> +};
> +
> +static ThrottleParamInfo throttle_iops_write_max_info = {
> +    THROTTLE_OPS_WRITE, DOUBLE, offsetof(LeakyBucket, max),
> +};
> +
> +static ThrottleParamInfo throttle_iops_write_max_length_info = {
> +    THROTTLE_OPS_WRITE, UNSIGNED, offsetof(LeakyBucket, burst_length),
> +};
> +
> +static ThrottleParamInfo throttle_bps_total_info = {
> +    THROTTLE_BPS_TOTAL, DOUBLE, offsetof(LeakyBucket, avg),
> +};
> +
> +static ThrottleParamInfo throttle_bps_total_max_info = {
> +    THROTTLE_BPS_TOTAL, DOUBLE, offsetof(LeakyBucket, max),
> +};
> +
> +static ThrottleParamInfo throttle_bps_total_max_length_info = {
> +    THROTTLE_BPS_TOTAL, UNSIGNED, offsetof(LeakyBucket, burst_length),
> +};
> +
> +static ThrottleParamInfo throttle_bps_read_info = {
> +    THROTTLE_BPS_READ, DOUBLE, offsetof(LeakyBucket, avg),
> +};
> +
> +static ThrottleParamInfo throttle_bps_read_max_info = {
> +    THROTTLE_BPS_READ, DOUBLE, offsetof(LeakyBucket, max),
> +};
> +
> +static ThrottleParamInfo throttle_bps_read_max_length_info = {
> +    THROTTLE_BPS_READ, UNSIGNED, offsetof(LeakyBucket, burst_length),
> +};
> +
> +static ThrottleParamInfo throttle_bps_write_info = {
> +    THROTTLE_BPS_WRITE, DOUBLE, offsetof(LeakyBucket, avg),
> +};
> +
> +static ThrottleParamInfo throttle_bps_write_max_info = {
> +    THROTTLE_BPS_WRITE, DOUBLE, offsetof(LeakyBucket, max),
> +};
> +
> +static ThrottleParamInfo throttle_bps_write_max_length_info = {
> +    THROTTLE_BPS_WRITE, UNSIGNED, offsetof(LeakyBucket, burst_length),
> +};
> +
> +static ThrottleParamInfo throttle_iops_size_info = {
> +    0, UINT64, offsetof(ThrottleConfig, op_size),
> +};
> +
> +static void throttle_group_obj_init(Object *obj)
> +{
> +    ThrottleGroup *tg = THROTTLE_GROUP(obj);
> +
> +    tg->clock_type = QEMU_CLOCK_REALTIME;
> +    if (qtest_enabled()) {
> +        /* For testing block IO throttling only */
> +        tg->clock_type = QEMU_CLOCK_VIRTUAL;
> +    }
> +    tg->is_initialized = false;
> +    qemu_mutex_init(&tg->lock);
> +    throttle_init(&tg->ts);
> +    QLIST_INIT(&tg->head);
> +}
> +
> +static void throttle_group_obj_complete(UserCreatable *obj, Error **errp)
> +{
> +    ThrottleGroup *tg = THROTTLE_GROUP(obj), *iter;
> +    ThrottleConfig *cfg = &tg->ts.cfg;
> +
> +    /* set group name to object id if it exists */
> +    if (!tg->name && tg->parent_obj.parent) {
> +        tg->name = object_get_canonical_path_component(OBJECT(obj));
> +    }
> +
> +    if (tg->name) {
> +        /* error if name is duplicate */
> +        QTAILQ_FOREACH(iter, &throttle_groups, list) {
> +            if (!g_strcmp0(tg->name, iter->name)) {
> +                error_setg(errp, "A group with this name already exists");
> +                goto fail;
> +            }
> +        }
> +    }
> +
> +    /* unfix buckets to check validity */
> +    throttle_get_config(&tg->ts, cfg);
> +    if (!throttle_is_valid(cfg, errp)) {
> +        goto fail;
> +    }
> +    /* fix buckets again */
> +    throttle_config(&tg->ts, tg->clock_type, cfg);
> +
> +    tg->is_initialized = true;
> +    QTAILQ_INSERT_TAIL(&throttle_groups, tg, list);
> +    return;
> +fail:
> +    qemu_mutex_destroy(&tg->lock);
> +    g_free(tg->name);

If throttle_group_obj_finalize() can still be called there will be
double-frees.  UserCreatable->complete() is not part of the core Object
lifecycle so I think it could happen and it's safer to leave tg fields
allocated so that throttle_group_obj_finalize() frees them once and only
once.

> +}
> +
> +static void throttle_group_obj_finalize(Object *obj)
> +{
> +    ThrottleGroup *tg = THROTTLE_GROUP(obj);
> +    qemu_mutex_lock(&throttle_groups_lock);
> +    QTAILQ_REMOVE(&throttle_groups, tg, list);
> +    qemu_mutex_unlock(&throttle_groups_lock);
> +    qemu_mutex_destroy(&tg->lock);
> +    g_free(tg->name);
> +}
> +
> +static void throttle_group_set(Object *obj, Visitor *v, const char * name,
> +        void *opaque, Error **errp)
> +
> +{
> +    ThrottleGroup *tg = THROTTLE_GROUP(obj);
> +    ThrottleConfig cfg;
> +    ThrottleParamInfo *info = opaque;
> +    ThrottleLimits *arg = NULL;
> +    Error *local_err = NULL;
> +    int64_t value;
> +
> +    if (!info) {
> +        arg = g_new0(ThrottleLimits, 1);
> +        visit_type_ThrottleLimits(v, name, &arg, &local_err);
> +        if (local_err) {
> +            goto fail;
> +        }
> +        qemu_mutex_lock(&tg->lock);
> +        throttle_get_config(&tg->ts, &cfg);
> +        throttle_limits_to_config(arg, &cfg, &local_err);
> +        if (local_err) {
> +            qemu_mutex_unlock(&tg->lock);
> +            goto fail;
> +        }
> +        throttle_config(&tg->ts, tg->clock_type, &cfg);
> +        qemu_mutex_unlock(&tg->lock);
> +
> +        goto ret;
> +    }

There is no need to share a function between ThrottleLimits and
individual fields.  The code would be clearer with two separate setter
functions.  The individual fields removal patch will also be cleaner
because it just needs to delete lines of code instead of modifying
throttle_group_set().

> +
> +    /* If we have finished initialization, don't accept individual property
> +     * changes through QOM. Throttle configuration limits must be set in one
> +     * transaction, as certain combinations are invalid.
> +     */
> +    if (tg->is_initialized) {
> +        error_setg(&local_err, "Property cannot be set after initialization");
> +        goto fail;
> +    }
> +
> +    visit_type_int64(v, name, &value, &local_err);
> +    if (local_err) {
> +        goto fail;
> +    }
> +    if (value < 0) {
> +        error_setg(&local_err, "Property value must be in range "
> +                               "[0, %"PRId64"]", INT64_MAX);

Please print the maximum value relevant to the actual field type instead
of INT64_MAX.

> +        goto fail;
> +    }
> +
> +    cfg = tg->ts.cfg;
> +    switch (info->data_type) {
> +    case UINT64:
> +        {
> +            uint64_t *field = (void *)&cfg.buckets[info->type] + info->offset;
> +            *field = value;
> +        }
> +        break;
> +    case DOUBLE:
> +        {
> +            double *field = (void *)&cfg.buckets[info->type] + info->offset;
> +            *field = value;
> +        }
> +        break;
> +    case UNSIGNED:
> +        {
> +            unsigned *field = (void *)&cfg.buckets[info->type] + info->offset;
> +            *field = value;
> +        }
> +    }
> +
> +    tg->ts.cfg = cfg;
> +    goto ret;
> +
> +fail:
> +    error_propagate(errp, local_err);
> +ret:
> +    g_free(arg);
> +    return;
> +
> +}
> +
> +static void throttle_group_get(Object *obj, Visitor *v, const char *name,
> +        void *opaque, Error **errp)
> +{
> +    ThrottleGroup *tg = THROTTLE_GROUP(obj);
> +    ThrottleConfig cfg;
> +    ThrottleParamInfo *info = opaque;
> +    ThrottleLimits *arg = NULL;
> +    int64_t value;
> +
> +    if (!info) {
> +        arg = g_new0(ThrottleLimits, 1);
> +        qemu_mutex_lock(&tg->lock);
> +        throttle_get_config(&tg->ts, &cfg);
> +        qemu_mutex_unlock(&tg->lock);
> +        throttle_config_to_throttle_limits(&cfg, arg);
> +        visit_type_ThrottleLimits(v, name, &arg, errp);
> +        g_free(arg);
> +        return;
> +    }
> +
> +    cfg = tg->ts.cfg;
> +    switch (info->data_type) {
> +    case UINT64:
> +        {
> +            uint64_t *field = (void *)&cfg.buckets[info->type] + info->offset;
> +            value = *field;
> +        }
> +        break;
> +    case DOUBLE:
> +        {
> +            double *field = (void *)&cfg.buckets[info->type] + info->offset;
> +            value = *field;
> +        }
> +        break;
> +    case UNSIGNED:
> +        {
> +            unsigned *field = (void *)&cfg.buckets[info->type] + info->offset;
> +            value = *field;
> +        }
> +    }
> +
> +    visit_type_int64(v, name, &value, errp);
> +}
> +
> +#undef THROTTLE_OPT_PREFIX
> +#define THROTTLE_OPT_PREFIX "x-"
> +static void throttle_group_obj_class_init(ObjectClass *klass, void *class_data)
> +{
> +    UserCreatableClass *ucc = USER_CREATABLE_CLASS(klass);
> +
> +    ucc->complete = throttle_group_obj_complete;
> +    /* iops limits */
> +    object_class_property_add(klass,
> +                              THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_TOTAL,
> +                              "int",
> +                              throttle_group_get,
> +                              throttle_group_set,
> +                              NULL, &throttle_iops_total_info,
> +                              &error_abort);
> +    object_class_property_add(klass,
> +                              THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_TOTAL_MAX,
> +                              "int",
> +                              throttle_group_get,
> +                              throttle_group_set,
> +                              NULL, &throttle_iops_total_max_info,
> +                              &error_abort);
> +    object_class_property_add(klass,
> +                              THROTTLE_OPT_PREFIX
> +                              QEMU_OPT_IOPS_TOTAL_MAX_LENGTH,
> +                              "int",
> +                              throttle_group_get,
> +                              throttle_group_set,
> +                              NULL, &throttle_iops_total_max_length_info,
> +                              &error_abort);
> +    object_class_property_add(klass,
> +                              THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_READ,
> +                              "int",
> +                              throttle_group_get,
> +                              throttle_group_set,
> +                              NULL, &throttle_iops_read_info,
> +                              &error_abort);
> +    object_class_property_add(klass,
> +                              THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_READ_MAX,
> +                              "int",
> +                              throttle_group_get,
> +                              throttle_group_set,
> +                              NULL, &throttle_iops_read_max_info,
> +                              &error_abort);
> +    object_class_property_add(klass,
> +                              THROTTLE_OPT_PREFIX
> +                              QEMU_OPT_IOPS_READ_MAX_LENGTH,
> +                              "int",
> +                              throttle_group_get,
> +                              throttle_group_set,
> +                              NULL, &throttle_iops_read_max_length_info,
> +                              &error_abort);
> +    object_class_property_add(klass,
> +                              THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_WRITE,
> +                              "int",
> +                              throttle_group_get,
> +                              throttle_group_set,
> +                              NULL, &throttle_iops_write_info, &error_abort);
> +    object_class_property_add(klass,
> +                              THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_WRITE_MAX,
> +                              "int",
> +                              throttle_group_get,
> +                              throttle_group_set,
> +                              NULL, &throttle_iops_write_max_info,
> +                              &error_abort);
> +    object_class_property_add(klass,
> +                              THROTTLE_OPT_PREFIX
> +                              QEMU_OPT_IOPS_WRITE_MAX_LENGTH,
> +                              "int",
> +                              throttle_group_get,
> +                              throttle_group_set,
> +                              NULL, &throttle_iops_write_max_length_info,
> +                              &error_abort);
> +    /* bps limits */
> +    object_class_property_add(klass,
> +                              THROTTLE_OPT_PREFIX QEMU_OPT_BPS_TOTAL,
> +                              "int",
> +                              throttle_group_get,
> +                              throttle_group_set,
> +                              NULL, &throttle_bps_total_info,
> +                              &error_abort);
> +    object_class_property_add(klass,
> +                              THROTTLE_OPT_PREFIX QEMU_OPT_BPS_TOTAL_MAX,
> +                              "int",
> +                              throttle_group_get,
> +                              throttle_group_set,
> +                              NULL, &throttle_bps_total_max_info,
> +                              &error_abort);
> +    object_class_property_add(klass,
> +                              THROTTLE_OPT_PREFIX
> +                              QEMU_OPT_BPS_TOTAL_MAX_LENGTH,
> +                              "int",
> +                              throttle_group_get,
> +                              throttle_group_set,
> +                              NULL, &throttle_bps_total_max_length_info,
> +                              &error_abort);
> +    object_class_property_add(klass,
> +                              THROTTLE_OPT_PREFIX QEMU_OPT_BPS_READ,
> +                              "int",
> +                              throttle_group_get,
> +                              throttle_group_set,
> +                              NULL, &throttle_bps_read_info,
> +                              &error_abort);
> +    object_class_property_add(klass,
> +                              THROTTLE_OPT_PREFIX QEMU_OPT_BPS_READ_MAX,
> +                              "int",
> +                              throttle_group_get,
> +                              throttle_group_set,
> +                              NULL, &throttle_bps_read_max_info,
> +                              &error_abort);
> +    object_class_property_add(klass,
> +                              THROTTLE_OPT_PREFIX QEMU_OPT_BPS_READ_MAX_LENGTH,
> +                              "int",
> +                              throttle_group_get,
> +                              throttle_group_set,
> +                              NULL, &throttle_bps_read_max_length_info,
> +                              &error_abort);
> +    object_class_property_add(klass,
> +                              THROTTLE_OPT_PREFIX QEMU_OPT_BPS_WRITE,
> +                              "int",
> +                              throttle_group_get,
> +                              throttle_group_set,
> +                              NULL, &throttle_bps_write_info,
> +                              &error_abort);
> +    object_class_property_add(klass,
> +                              THROTTLE_OPT_PREFIX QEMU_OPT_BPS_WRITE_MAX,
> +                              "int",
> +                              throttle_group_get,
> +                              throttle_group_set,
> +                              NULL, &throttle_bps_write_max_info,
> +                              &error_abort);
> +    object_class_property_add(klass,
> +                              THROTTLE_OPT_PREFIX
> +                              QEMU_OPT_BPS_WRITE_MAX_LENGTH,
> +                              "int",
> +                              throttle_group_get,
> +                              throttle_group_set,
> +                              NULL, &throttle_bps_write_max_length_info,
> +                              &error_abort);
> +    /* rest */
> +    object_class_property_add(klass,
> +                              THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_SIZE,
> +                              "int",
> +                              throttle_group_get,
> +                              throttle_group_set,
> +                              NULL, &throttle_iops_size_info,
> +                              &error_abort);
> +
> +    /* ThrottleLimits */
> +    object_class_property_add(klass,
> +                              "limits", "ThrottleLimits",
> +                              throttle_group_get,
> +                              throttle_group_set,
> +                              NULL, NULL,
> +                              &error_abort);
> +}
> +
> +static const TypeInfo throttle_group_info = {
> +   .name = TYPE_THROTTLE_GROUP,
> +   .parent = TYPE_OBJECT,
> +   .class_init = throttle_group_obj_class_init,
> +   .instance_size = sizeof(ThrottleGroup),
> +   .instance_init = throttle_group_obj_init,
> +   .instance_finalize = throttle_group_obj_finalize,
> +   .interfaces = (InterfaceInfo[]) {
> +       { TYPE_USER_CREATABLE },
> +       { }
> +   },
> +};
> +
>  static void throttle_groups_init(void)
>  {
>      qemu_mutex_init(&throttle_groups_lock);
> +    type_register_static(&throttle_group_info);
>  }
>  
> -block_init(throttle_groups_init);
> +type_init(throttle_groups_init);
> diff --git a/include/block/throttle-groups.h b/include/block/throttle-groups.h
> index a0f27cac63..096d05ef92 100644
> --- a/include/block/throttle-groups.h
> +++ b/include/block/throttle-groups.h
> @@ -53,6 +53,9 @@ typedef struct ThrottleGroupMember {
>  
>  } ThrottleGroupMember;
>  
> +#define TYPE_THROTTLE_GROUP "throttling-group"
> +#define THROTTLE_GROUP(obj) OBJECT_CHECK(ThrottleGroup, (obj), TYPE_THROTTLE_GROUP)
> +
>  const char *throttle_group_get_name(ThrottleGroupMember *tgm);
>  
>  ThrottleState *throttle_group_incref(const char *name);
> diff --git a/include/qemu/throttle-options.h b/include/qemu/throttle-options.h
> index 3133d1ca40..182b7896e1 100644
> --- a/include/qemu/throttle-options.h
> +++ b/include/qemu/throttle-options.h
> @@ -10,81 +10,102 @@
>  #ifndef THROTTLE_OPTIONS_H
>  #define THROTTLE_OPTIONS_H
>  
> +#define QEMU_OPT_IOPS_TOTAL "iops-total"
> +#define QEMU_OPT_IOPS_TOTAL_MAX "iops-total-max"
> +#define QEMU_OPT_IOPS_TOTAL_MAX_LENGTH "iops-total-max-length"
> +#define QEMU_OPT_IOPS_READ "iops-read"
> +#define QEMU_OPT_IOPS_READ_MAX "iops-read-max"
> +#define QEMU_OPT_IOPS_READ_MAX_LENGTH "iops-read-max-length"
> +#define QEMU_OPT_IOPS_WRITE "iops-write"
> +#define QEMU_OPT_IOPS_WRITE_MAX "iops-write-max"
> +#define QEMU_OPT_IOPS_WRITE_MAX_LENGTH "iops-write-max-length"
> +#define QEMU_OPT_BPS_TOTAL "bps-total"
> +#define QEMU_OPT_BPS_TOTAL_MAX "bps-total-max"
> +#define QEMU_OPT_BPS_TOTAL_MAX_LENGTH "bps-total-max-length"
> +#define QEMU_OPT_BPS_READ "bps-read"
> +#define QEMU_OPT_BPS_READ_MAX "bps-read-max"
> +#define QEMU_OPT_BPS_READ_MAX_LENGTH "bps-read-max-length"
> +#define QEMU_OPT_BPS_WRITE "bps-write"
> +#define QEMU_OPT_BPS_WRITE_MAX "bps-write-max"
> +#define QEMU_OPT_BPS_WRITE_MAX_LENGTH "bps-write-max-length"
> +#define QEMU_OPT_IOPS_SIZE "iops-size"
> +
> +#define THROTTLE_OPT_PREFIX "throttling."
>  #define THROTTLE_OPTS \
>            { \
> -            .name = "throttling.iops-total",\
> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_TOTAL,\
>              .type = QEMU_OPT_NUMBER,\
>              .help = "limit total I/O operations per second",\
>          },{ \
> -            .name = "throttling.iops-read",\
> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_READ,\
>              .type = QEMU_OPT_NUMBER,\
>              .help = "limit read operations per second",\
>          },{ \
> -            .name = "throttling.iops-write",\
> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_WRITE,\
>              .type = QEMU_OPT_NUMBER,\
>              .help = "limit write operations per second",\
>          },{ \
> -            .name = "throttling.bps-total",\
> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_BPS_TOTAL,\
>              .type = QEMU_OPT_NUMBER,\
>              .help = "limit total bytes per second",\
>          },{ \
> -            .name = "throttling.bps-read",\
> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_BPS_READ,\
>              .type = QEMU_OPT_NUMBER,\
>              .help = "limit read bytes per second",\
>          },{ \
> -            .name = "throttling.bps-write",\
> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_BPS_WRITE,\
>              .type = QEMU_OPT_NUMBER,\
>              .help = "limit write bytes per second",\
>          },{ \
> -            .name = "throttling.iops-total-max",\
> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_TOTAL_MAX,\
>              .type = QEMU_OPT_NUMBER,\
>              .help = "I/O operations burst",\
>          },{ \
> -            .name = "throttling.iops-read-max",\
> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_READ_MAX,\
>              .type = QEMU_OPT_NUMBER,\
>              .help = "I/O operations read burst",\
>          },{ \
> -            .name = "throttling.iops-write-max",\
> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_WRITE_MAX,\
>              .type = QEMU_OPT_NUMBER,\
>              .help = "I/O operations write burst",\
>          },{ \
> -            .name = "throttling.bps-total-max",\
> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_BPS_TOTAL_MAX,\
>              .type = QEMU_OPT_NUMBER,\
>              .help = "total bytes burst",\
>          },{ \
> -            .name = "throttling.bps-read-max",\
> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_BPS_READ_MAX,\
>              .type = QEMU_OPT_NUMBER,\
>              .help = "total bytes read burst",\
>          },{ \
> -            .name = "throttling.bps-write-max",\
> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_BPS_WRITE_MAX,\
>              .type = QEMU_OPT_NUMBER,\
>              .help = "total bytes write burst",\
>          },{ \
> -            .name = "throttling.iops-total-max-length",\
> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_TOTAL_MAX_LENGTH,\
>              .type = QEMU_OPT_NUMBER,\
>              .help = "length of the iops-total-max burst period, in seconds",\
>          },{ \
> -            .name = "throttling.iops-read-max-length",\
> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_READ_MAX_LENGTH,\
>              .type = QEMU_OPT_NUMBER,\
>              .help = "length of the iops-read-max burst period, in seconds",\
>          },{ \
> -            .name = "throttling.iops-write-max-length",\
> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_WRITE_MAX_LENGTH,\
>              .type = QEMU_OPT_NUMBER,\
>              .help = "length of the iops-write-max burst period, in seconds",\
>          },{ \
> -            .name = "throttling.bps-total-max-length",\
> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_BPS_TOTAL_MAX_LENGTH,\
>              .type = QEMU_OPT_NUMBER,\
>              .help = "length of the bps-total-max burst period, in seconds",\
>          },{ \
> -            .name = "throttling.bps-read-max-length",\
> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_BPS_READ_MAX_LENGTH,\
>              .type = QEMU_OPT_NUMBER,\
>              .help = "length of the bps-read-max burst period, in seconds",\
>          },{ \
> -            .name = "throttling.bps-write-max-length",\
> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_BPS_WRITE_MAX_LENGTH,\
>              .type = QEMU_OPT_NUMBER,\
>              .help = "length of the bps-write-max burst period, in seconds",\
>          },{ \
> -            .name = "throttling.iops-size",\
> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_SIZE,\
>              .type = QEMU_OPT_NUMBER,\
>              .help = "when limiting by iops max size of an I/O in bytes",\
>          }
> diff --git a/include/qemu/throttle.h b/include/qemu/throttle.h
> index d056008c18..17e750b12d 100644
> --- a/include/qemu/throttle.h
> +++ b/include/qemu/throttle.h
> @@ -152,5 +152,8 @@ bool throttle_schedule_timer(ThrottleState *ts,
>                               bool is_write);
>  
>  void throttle_account(ThrottleState *ts, bool is_write, uint64_t size);
> +void throttle_limits_to_config(ThrottleLimits *arg, ThrottleConfig *cfg,
> +                               Error **errp);
> +void throttle_config_to_throttle_limits(ThrottleConfig *cfg, ThrottleLimits *var);
>  
>  #endif
> diff --git a/qapi/block-core.json b/qapi/block-core.json
> index c437aa50ef..1084158b6a 100644
> --- a/qapi/block-core.json
> +++ b/qapi/block-core.json
> @@ -1896,6 +1896,51 @@
>              '*iops_size': 'int', '*group': 'str' } }
>  
>  ##
> +# @ThrottleLimits:
> +#
> +# Limit parameters for throttling.

Please add a note that atomic updates are recommended.  Even though
fields are optional it's best to specify all fields one wishes to change
at once instead of performing multiple changes.

> +#
> +# @iops-total:             limit total I/O operations per second
> +# @iops-total-max:         I/O operations burst
> +# @iops-total-max-length:  length of the iops-total-max burst period, in seconds
> +#                          It must only be set if @iops-total-max is set as well.
> +# @iops-read:              limit read operations per second
> +# @iops-read-max:          I/O operations read burst
> +# @iops-read-max-length:   length of the iops-read-max burst period, in seconds
> +#                          It must only be set if @iops-read-max is set as well.
> +# @iops-write:             limit write operations per second
> +# @iops-write-max:         I/O operations write burst
> +# @iops-write-max-length:  length of the iops-write-max burst period, in seconds
> +#                          It must only be set if @iops-write-max is set as well.
> +# @bps-total:              limit total bytes per second
> +# @bps-total-max:          total bytes burst
> +# @bps-total-max-length:   length of the bps-total-max burst period, in seconds.
> +#                          It must only be set if @bps-total-max is set as well.
> +# @bps-read:               limit read bytes per second
> +# @bps-read-max:           total bytes read burst
> +# @bps-read-max-length:    length of the bps-read-max burst period, in seconds
> +#                          It must only be set if @bps-read-max is set as well.
> +# @bps-write:              limit write bytes per second
> +# @bps-write-max:          total bytes write burst
> +# @bps-write-max-length:   length of the bps-write-max burst period, in seconds
> +#                          It must only be set if @bps-write-max is set as well.
> +# @iops-size:              when limiting by iops max size of an I/O in bytes
> +#
> +# Since: 2.10
> +##
> +{ 'struct': 'ThrottleLimits',
> +  'data': { '*iops-total' : 'int', '*iops-total-max' : 'int',
> +            '*iops-total-max-length' : 'int', '*iops-read' : 'int',
> +            '*iops-read-max' : 'int', '*iops-read-max-length' : 'int',
> +            '*iops-write' : 'int', '*iops-write-max' : 'int',
> +            '*iops-write-max-length' : 'int', '*bps-total' : 'int',
> +            '*bps-total-max' : 'int', '*bps-total-max-length' : 'int',
> +            '*bps-read' : 'int', '*bps-read-max' : 'int',
> +            '*bps-read-max-length' : 'int', '*bps-write' : 'int',
> +            '*bps-write-max' : 'int', '*bps-write-max-length' : 'int',
> +            '*iops-size' : 'int' } }
> +
> +##
>  # @block-stream:
>  #
>  # Copy data from a backing file into a block device.
> diff --git a/tests/test-throttle.c b/tests/test-throttle.c
> index 57cf5ba711..0ea9093eee 100644
> --- a/tests/test-throttle.c
> +++ b/tests/test-throttle.c
> @@ -662,6 +662,7 @@ int main(int argc, char **argv)
>      qemu_init_main_loop(&error_fatal);
>      ctx = qemu_get_aio_context();
>      bdrv_init();
> +    module_call_init(MODULE_INIT_QOM);
>  
>      do {} while (g_main_context_iteration(NULL, false));
>  
> diff --git a/util/throttle.c b/util/throttle.c
> index b2a52b8b34..3bb21a63e6 100644
> --- a/util/throttle.c
> +++ b/util/throttle.c
> @@ -502,3 +502,124 @@ void throttle_account(ThrottleState *ts, bool is_write, uint64_t size)
>      }
>  }
>  
> +/* return a ThrottleConfig based on the options in a ThrottleLimits
> + *
> + * @arg:    the ThrottleLimits object to read from
> + * @cfg:    the ThrottleConfig to edit
> + * @errp:   error object
> + */
> +void throttle_limits_to_config(ThrottleLimits *arg, ThrottleConfig *cfg,
> +                               Error **errp)
> +{
> +    if (arg->has_bps_total) {
> +        cfg->buckets[THROTTLE_BPS_TOTAL].avg = arg->bps_total;
> +    }
> +    if (arg->has_bps_read) {
> +        cfg->buckets[THROTTLE_BPS_READ].avg  = arg->bps_read;
> +    }
> +    if (arg->has_bps_write) {
> +        cfg->buckets[THROTTLE_BPS_WRITE].avg = arg->bps_write;
> +    }
> +
> +    if (arg->has_iops_total) {
> +        cfg->buckets[THROTTLE_OPS_TOTAL].avg = arg->iops_total;
> +    }
> +    if (arg->has_iops_read) {
> +        cfg->buckets[THROTTLE_OPS_READ].avg  = arg->iops_read;
> +    }
> +    if (arg->has_iops_write) {
> +        cfg->buckets[THROTTLE_OPS_WRITE].avg = arg->iops_write;
> +    }
> +
> +    if (arg->has_bps_total_max) {
> +        cfg->buckets[THROTTLE_BPS_TOTAL].max = arg->bps_total_max;
> +    }
> +    if (arg->has_bps_read_max) {
> +        cfg->buckets[THROTTLE_BPS_READ].max = arg->bps_read_max;
> +    }
> +    if (arg->has_bps_write_max) {
> +        cfg->buckets[THROTTLE_BPS_WRITE].max = arg->bps_write_max;
> +    }
> +    if (arg->has_iops_total_max) {
> +        cfg->buckets[THROTTLE_OPS_TOTAL].max = arg->iops_total_max;
> +    }
> +    if (arg->has_iops_read_max) {
> +        cfg->buckets[THROTTLE_OPS_READ].max = arg->iops_read_max;
> +    }
> +    if (arg->has_iops_write_max) {
> +        cfg->buckets[THROTTLE_OPS_WRITE].max = arg->iops_write_max;
> +    }
> +
> +    if (arg->has_bps_total_max_length) {
> +        cfg->buckets[THROTTLE_BPS_TOTAL].burst_length = arg->bps_total_max_length;
> +    }
> +    if (arg->has_bps_read_max_length) {
> +        cfg->buckets[THROTTLE_BPS_READ].burst_length = arg->bps_read_max_length;
> +    }
> +    if (arg->has_bps_write_max_length) {
> +        cfg->buckets[THROTTLE_BPS_WRITE].burst_length = arg->bps_write_max_length;
> +    }
> +    if (arg->has_iops_total_max_length) {
> +        cfg->buckets[THROTTLE_OPS_TOTAL].burst_length = arg->iops_total_max_length;
> +    }
> +    if (arg->has_iops_read_max_length) {
> +        cfg->buckets[THROTTLE_OPS_READ].burst_length = arg->iops_read_max_length;
> +    }
> +    if (arg->has_iops_write_max_length) {
> +        cfg->buckets[THROTTLE_OPS_WRITE].burst_length = arg->iops_write_max_length;
> +    }
> +
> +    if (arg->has_iops_size) {
> +        cfg->op_size = arg->iops_size;
> +    }
> +
> +    throttle_is_valid(cfg, errp);
> +}
> +
> +/* write the options of a ThrottleConfig to a ThrottleLimits
> + *
> + * @cfg:    the ThrottleConfig to read from
> + * @var:    the ThrottleLimits to write to
> + */
> +void throttle_config_to_throttle_limits(ThrottleConfig *cfg, ThrottleLimits *var)
> +{
> +    var->bps_total               = cfg->buckets[THROTTLE_BPS_TOTAL].avg;
> +    var->bps_read                = cfg->buckets[THROTTLE_BPS_READ].avg;
> +    var->bps_write               = cfg->buckets[THROTTLE_BPS_WRITE].avg;
> +    var->iops_total              = cfg->buckets[THROTTLE_OPS_TOTAL].avg;
> +    var->iops_read               = cfg->buckets[THROTTLE_OPS_READ].avg;
> +    var->iops_write              = cfg->buckets[THROTTLE_OPS_WRITE].avg;
> +    var->bps_total_max           = cfg->buckets[THROTTLE_BPS_TOTAL].max;
> +    var->bps_read_max            = cfg->buckets[THROTTLE_BPS_READ].max;
> +    var->bps_write_max           = cfg->buckets[THROTTLE_BPS_WRITE].max;
> +    var->iops_total_max          = cfg->buckets[THROTTLE_OPS_TOTAL].max;
> +    var->iops_read_max           = cfg->buckets[THROTTLE_OPS_READ].max;
> +    var->iops_write_max          = cfg->buckets[THROTTLE_OPS_WRITE].max;
> +    var->bps_total_max_length    = cfg->buckets[THROTTLE_BPS_TOTAL].burst_length;
> +    var->bps_read_max_length     = cfg->buckets[THROTTLE_BPS_READ].burst_length;
> +    var->bps_write_max_length    = cfg->buckets[THROTTLE_BPS_WRITE].burst_length;
> +    var->iops_total_max_length   = cfg->buckets[THROTTLE_OPS_TOTAL].burst_length;
> +    var->iops_read_max_length    = cfg->buckets[THROTTLE_OPS_READ].burst_length;
> +    var->iops_write_max_length   = cfg->buckets[THROTTLE_OPS_WRITE].burst_length;
> +    var->iops_size               = cfg->op_size;
> +
> +    var->has_bps_total = true;
> +    var->has_bps_read = true;
> +    var->has_bps_write = true;
> +    var->has_iops_total = true;
> +    var->has_iops_read = true;
> +    var->has_iops_write = true;
> +    var->has_bps_total_max = true;
> +    var->has_bps_read_max = true;
> +    var->has_bps_write_max = true;
> +    var->has_iops_total_max = true;
> +    var->has_iops_read_max = true;
> +    var->has_iops_write_max = true;
> +    var->has_bps_read_max_length = true;
> +    var->has_bps_total_max_length = true;
> +    var->has_bps_write_max_length = true;
> +    var->has_iops_total_max_length = true;
> +    var->has_iops_read_max_length = true;
> +    var->has_iops_write_max_length = true;
> +    var->has_iops_size = true;
> +}
> -- 
> 2.11.0
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 455 bytes --]

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [Qemu-devel] [PATCH 4/7] block: convert ThrottleGroup to object with QOM
  2017-07-24 15:12   ` Stefan Hajnoczi
@ 2017-07-25 10:29     ` Manos Pitsidianakis
  2017-07-25 16:09       ` [Qemu-devel] [Qemu-block] " Stefan Hajnoczi
  0 siblings, 1 reply; 17+ messages in thread
From: Manos Pitsidianakis @ 2017-07-25 10:29 UTC (permalink / raw)
  To: Stefan Hajnoczi; +Cc: qemu-devel, Kevin Wolf, Alberto Garcia, qemu-block

[-- Attachment #1: Type: text/plain, Size: 44556 bytes --]

On Mon, Jul 24, 2017 at 04:12:47PM +0100, Stefan Hajnoczi wrote:
>On Fri, Jul 14, 2017 at 12:45:18PM +0300, Manos Pitsidianakis wrote:
>> ThrottleGroup is converted to an object. This will allow the future
>> throttle block filter drive easy creation and configuration of throttle
>> groups in QMP and cli.
>>
>> A new QAPI struct, ThrottleLimits, is introduced to provide a shared
>> struct for all throttle configuration needs in QMP.
>>
>> ThrottleGroups can be created via CLI as
>>     -object throttling-group,id=foo,x-iops-total=100,x-..
>
>Please make the QOM name and struct name consistent.  Either
>ThrottleGroup/throttle-group or ThrottlingGroup/throttling-group but not
>ThrottleGroup/throttling-group.

I did this on purpose because current throttling has ThrottleGroup 
internally and throttling.group in the command line. Should we keep this 
only in legacy and make it throttle-group everywhere?

>> where x-* are individual limit properties. Since we can't add non-scalar
>> properties in -object this interface must be used instead. However,
>> setting these properties must be disabled after initialization because
>> certain combinations of limits are forbidden and thus configuration
>> changes should be done in one transaction. The individual properties
>> will go away when support for non-scalar values in CLI is implemented
>> and thus are marked as experimental.
>>
>> ThrottleGroup also has a `limits` property that uses the ThrottleLimits
>> struct.  It can be used to create ThrottleGroups or set the
>> configuration in existing groups as follows:
>>
>> { "execute": "object-add",
>>   "arguments": {
>>     "qom-type": "throttling-group",
>>     "id": "foo",
>>     "props" : {
>>       "limits": {
>>           "iops-total": 100
>>       }
>>     }
>>   }
>> }
>> { "execute" : "qom-set",
>>     "arguments" : {
>>         "path" : "foo",
>>         "property" : "limits",
>>         "value" : {
>>             "iops-total" : 99
>>         }
>>     }
>> }
>>
>> This also means a group's configuration can be fetched with qom-get.
>>
>> ThrottleGroups can be anonymous which means they can't get accessed by
>> other users ie they will always be units instead of group (Because they
>> have one ThrottleGroupMember).
>>
>> Signed-off-by: Manos Pitsidianakis <el13635@mail.ntua.gr>
>> ---
>>
>> Notes:
>>     Note: I tested Markus Armbruster's patch in <87wp7fghi9.fsf@dusky.pond.sub.org>
>>     on master and I can use this syntax successfuly:
>>     -object '{ "qom-type" : "throttling-group", "id" : "foo", "props" : { "limits" \
>>     : { "iops-total" : 1000 } } }'
>>     If this gets merged using -object will be a little more verbose but at least we
>>     won't have seperate properties, which is a good thing, so the x-* should be
>>     dropped.
>>
>>  block/throttle-groups.c         | 507 +++++++++++++++++++++++++++++++++++++---
>>  include/block/throttle-groups.h |   3 +
>>  include/qemu/throttle-options.h |  59 +++--
>>  include/qemu/throttle.h         |   3 +
>>  qapi/block-core.json            |  45 ++++
>>  tests/test-throttle.c           |   1 +
>>  util/throttle.c                 | 121 ++++++++++
>>  7 files changed, 689 insertions(+), 50 deletions(-)
>>
>> diff --git a/block/throttle-groups.c b/block/throttle-groups.c
>> index f711a3dc62..4d1f82ec06 100644
>> --- a/block/throttle-groups.c
>> +++ b/block/throttle-groups.c
>> @@ -25,9 +25,17 @@
>>  #include "qemu/osdep.h"
>>  #include "sysemu/block-backend.h"
>>  #include "block/throttle-groups.h"
>> +#include "qemu/throttle-options.h"
>>  #include "qemu/queue.h"
>>  #include "qemu/thread.h"
>>  #include "sysemu/qtest.h"
>> +#include "qapi/error.h"
>> +#include "qapi-visit.h"
>> +#include "qom/object.h"
>> +#include "qom/object_interfaces.h"
>> +
>> +static void throttle_group_obj_init(Object *obj);
>> +static void throttle_group_obj_complete(UserCreatable *obj, Error **errp);
>>
>>  /* The ThrottleGroup structure (with its ThrottleState) is shared
>>   * among different ThrottleGroupMembers and it's independent from
>> @@ -54,6 +62,10 @@
>>   * that ThrottleGroupMember has throttled requests in the queue.
>>   */
>>  typedef struct ThrottleGroup {
>> +    Object parent_obj;
>> +
>> +    /* refuse individual property change if initialization is complete */
>> +    bool is_initialized;
>>      char *name; /* This is constant during the lifetime of the group */
>>
>>      QemuMutex lock; /* This lock protects the following four fields */
>> @@ -63,8 +75,7 @@ typedef struct ThrottleGroup {
>>      bool any_timer_armed[2];
>>      QEMUClockType clock_type;
>>
>> -    /* These two are protected by the global throttle_groups_lock */
>> -    unsigned refcount;
>> +    /* This is protected by the global throttle_groups_lock */
>>      QTAILQ_ENTRY(ThrottleGroup) list;
>>  } ThrottleGroup;
>>
>> @@ -77,7 +88,8 @@ static QTAILQ_HEAD(, ThrottleGroup) throttle_groups =
>>   * If no ThrottleGroup is found with the given name a new one is
>>   * created.
>>   *
>> - * @name: the name of the ThrottleGroup
>> + * @name: the name of the ThrottleGroup, NULL means a new anonymous group will
>> + *        be created.
>>   * @ret:  the ThrottleState member of the ThrottleGroup
>>   */
>>  ThrottleState *throttle_group_incref(const char *name)
>> @@ -87,32 +99,30 @@ ThrottleState *throttle_group_incref(const char *name)
>>
>>      qemu_mutex_lock(&throttle_groups_lock);
>>
>> -    /* Look for an existing group with that name */
>> -    QTAILQ_FOREACH(iter, &throttle_groups, list) {
>> -        if (!strcmp(name, iter->name)) {
>> -            tg = iter;
>> -            break;
>> +    if (name) {
>> +        /* Look for an existing group with that name */
>> +        QTAILQ_FOREACH(iter, &throttle_groups, list) {
>> +            if (!g_strcmp0(name, iter->name)) {
>> +                tg = iter;
>> +                break;
>> +            }
>>          }
>>      }
>>
>>      /* Create a new one if not found */
>>      if (!tg) {
>> -        tg = g_new0(ThrottleGroup, 1);
>> +        /* new ThrottleGroup obj will have a refcnt = 1 */
>> +        tg = THROTTLE_GROUP(object_new(TYPE_THROTTLE_GROUP));
>>          tg->name = g_strdup(name);
>> -        tg->clock_type = QEMU_CLOCK_REALTIME;
>> -
>> -        if (qtest_enabled()) {
>> -            /* For testing block IO throttling only */
>> -            tg->clock_type = QEMU_CLOCK_VIRTUAL;
>> -        }
>> -        qemu_mutex_init(&tg->lock);
>> -        throttle_init(&tg->ts);
>> -        QLIST_INIT(&tg->head);
>> -
>> -        QTAILQ_INSERT_TAIL(&throttle_groups, tg, list);
>> +        throttle_group_obj_complete((UserCreatable *)tg, &error_abort);
>>      }
>>
>> -    tg->refcount++;
>> +    qemu_mutex_lock(&tg->lock);
>> +    if (!QLIST_EMPTY(&tg->head)) {
>> +        /* only ref if the group is not empty */
>> +        object_ref(OBJECT(tg));
>> +    }
>> +    qemu_mutex_unlock(&tg->lock);
>>
>>      qemu_mutex_unlock(&throttle_groups_lock);
>>
>> @@ -129,15 +139,7 @@ ThrottleState *throttle_group_incref(const char *name)
>>  void throttle_group_unref(ThrottleState *ts)
>>  {
>>      ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts);
>> -
>> -    qemu_mutex_lock(&throttle_groups_lock);
>> -    if (--tg->refcount == 0) {
>> -        QTAILQ_REMOVE(&throttle_groups, tg, list);
>> -        qemu_mutex_destroy(&tg->lock);
>> -        g_free(tg->name);
>> -        g_free(tg);
>> -    }
>> -    qemu_mutex_unlock(&throttle_groups_lock);
>> +    object_unref(OBJECT(tg));
>>  }
>>
>>  /* Get the name from a ThrottleGroupMember's group. The name (and the pointer)
>> @@ -572,9 +574,452 @@ void throttle_group_detach_aio_context(ThrottleGroupMember *tgm)
>>      throttle_timers_detach_aio_context(tt);
>>  }
>>
>> +#define DOUBLE 0
>> +#define UINT64 1
>> +#define UNSIGNED 2
>> +
>> +typedef struct {
>> +    BucketType type;
>> +    int data_type;
>> +    ptrdiff_t offset; /* offset in LeakyBucket struct. */
>> +} ThrottleParamInfo;
>> +
>> +static ThrottleParamInfo throttle_iops_total_info = {
>
>These can be made const.  This offers a tiny security benefit because
>.offset will be read-only and therefore cannot be used to overwrite
>arbitrary memory.  Exploits often gain control of execution by first
>overwriting a function pointer or pointer field that they can influence.
>
>Also, how about defining an array and looping over it to avoid
>repetition?
>
>  for (i = 0; i < ARRAY_SIZE(params); i++) {
>      object_class_property_add(klass,
>                                params[i].name,
>                                "int",
>                                throttle_group_get,
>                                throttle_group_set,
>                                NULL, &params[i],
>                                &error_abort);
>  }

Thanks, I will look into factoring these in.

>> +    THROTTLE_OPS_TOTAL, DOUBLE, offsetof(LeakyBucket, avg),
>> +};
>> +
>> +static ThrottleParamInfo throttle_iops_total_max_info = {
>> +    THROTTLE_OPS_TOTAL, DOUBLE, offsetof(LeakyBucket, max),
>> +};
>> +
>> +static ThrottleParamInfo throttle_iops_total_max_length_info = {
>> +    THROTTLE_OPS_TOTAL, UNSIGNED, offsetof(LeakyBucket, burst_length),
>> +};
>> +
>> +static ThrottleParamInfo throttle_iops_read_info = {
>> +    THROTTLE_OPS_READ, DOUBLE, offsetof(LeakyBucket, avg),
>> +};
>> +
>> +static ThrottleParamInfo throttle_iops_read_max_info = {
>> +    THROTTLE_OPS_READ, DOUBLE, offsetof(LeakyBucket, max),
>> +};
>> +
>> +static ThrottleParamInfo throttle_iops_read_max_length_info = {
>> +    THROTTLE_OPS_READ, UNSIGNED, offsetof(LeakyBucket, burst_length),
>> +};
>> +
>> +static ThrottleParamInfo throttle_iops_write_info = {
>> +    THROTTLE_OPS_WRITE, DOUBLE, offsetof(LeakyBucket, avg),
>> +};
>> +
>> +static ThrottleParamInfo throttle_iops_write_max_info = {
>> +    THROTTLE_OPS_WRITE, DOUBLE, offsetof(LeakyBucket, max),
>> +};
>> +
>> +static ThrottleParamInfo throttle_iops_write_max_length_info = {
>> +    THROTTLE_OPS_WRITE, UNSIGNED, offsetof(LeakyBucket, burst_length),
>> +};
>> +
>> +static ThrottleParamInfo throttle_bps_total_info = {
>> +    THROTTLE_BPS_TOTAL, DOUBLE, offsetof(LeakyBucket, avg),
>> +};
>> +
>> +static ThrottleParamInfo throttle_bps_total_max_info = {
>> +    THROTTLE_BPS_TOTAL, DOUBLE, offsetof(LeakyBucket, max),
>> +};
>> +
>> +static ThrottleParamInfo throttle_bps_total_max_length_info = {
>> +    THROTTLE_BPS_TOTAL, UNSIGNED, offsetof(LeakyBucket, burst_length),
>> +};
>> +
>> +static ThrottleParamInfo throttle_bps_read_info = {
>> +    THROTTLE_BPS_READ, DOUBLE, offsetof(LeakyBucket, avg),
>> +};
>> +
>> +static ThrottleParamInfo throttle_bps_read_max_info = {
>> +    THROTTLE_BPS_READ, DOUBLE, offsetof(LeakyBucket, max),
>> +};
>> +
>> +static ThrottleParamInfo throttle_bps_read_max_length_info = {
>> +    THROTTLE_BPS_READ, UNSIGNED, offsetof(LeakyBucket, burst_length),
>> +};
>> +
>> +static ThrottleParamInfo throttle_bps_write_info = {
>> +    THROTTLE_BPS_WRITE, DOUBLE, offsetof(LeakyBucket, avg),
>> +};
>> +
>> +static ThrottleParamInfo throttle_bps_write_max_info = {
>> +    THROTTLE_BPS_WRITE, DOUBLE, offsetof(LeakyBucket, max),
>> +};
>> +
>> +static ThrottleParamInfo throttle_bps_write_max_length_info = {
>> +    THROTTLE_BPS_WRITE, UNSIGNED, offsetof(LeakyBucket, burst_length),
>> +};
>> +
>> +static ThrottleParamInfo throttle_iops_size_info = {
>> +    0, UINT64, offsetof(ThrottleConfig, op_size),
>> +};
>> +
>> +static void throttle_group_obj_init(Object *obj)
>> +{
>> +    ThrottleGroup *tg = THROTTLE_GROUP(obj);
>> +
>> +    tg->clock_type = QEMU_CLOCK_REALTIME;
>> +    if (qtest_enabled()) {
>> +        /* For testing block IO throttling only */
>> +        tg->clock_type = QEMU_CLOCK_VIRTUAL;
>> +    }
>> +    tg->is_initialized = false;
>> +    qemu_mutex_init(&tg->lock);
>> +    throttle_init(&tg->ts);
>> +    QLIST_INIT(&tg->head);
>> +}
>> +
>> +static void throttle_group_obj_complete(UserCreatable *obj, Error **errp)
>> +{
>> +    ThrottleGroup *tg = THROTTLE_GROUP(obj), *iter;
>> +    ThrottleConfig *cfg = &tg->ts.cfg;
>> +
>> +    /* set group name to object id if it exists */
>> +    if (!tg->name && tg->parent_obj.parent) {
>> +        tg->name = object_get_canonical_path_component(OBJECT(obj));
>> +    }
>> +
>> +    if (tg->name) {
>> +        /* error if name is duplicate */
>> +        QTAILQ_FOREACH(iter, &throttle_groups, list) {
>> +            if (!g_strcmp0(tg->name, iter->name)) {
>> +                error_setg(errp, "A group with this name already exists");
>> +                goto fail;
>> +            }
>> +        }
>> +    }
>> +
>> +    /* unfix buckets to check validity */
>> +    throttle_get_config(&tg->ts, cfg);
>> +    if (!throttle_is_valid(cfg, errp)) {
>> +        goto fail;
>> +    }
>> +    /* fix buckets again */
>> +    throttle_config(&tg->ts, tg->clock_type, cfg);
>> +
>> +    tg->is_initialized = true;
>> +    QTAILQ_INSERT_TAIL(&throttle_groups, tg, list);
>> +    return;
>> +fail:
>> +    qemu_mutex_destroy(&tg->lock);
>> +    g_free(tg->name);
>
>If throttle_group_obj_finalize() can still be called there will be
>double-frees.  UserCreatable->complete() is not part of the core Object
>lifecycle so I think it could happen and it's safer to leave tg fields
>allocated so that throttle_group_obj_finalize() frees them once and only
>once.

Yeah it seems that it is called on error in QMP with object-add but not 
in CLI, thanks.

>> +}
>> +
>> +static void throttle_group_obj_finalize(Object *obj)
>> +{
>> +    ThrottleGroup *tg = THROTTLE_GROUP(obj);
>> +    qemu_mutex_lock(&throttle_groups_lock);
>> +    QTAILQ_REMOVE(&throttle_groups, tg, list);
>> +    qemu_mutex_unlock(&throttle_groups_lock);
>> +    qemu_mutex_destroy(&tg->lock);
>> +    g_free(tg->name);
>> +}
>> +
>> +static void throttle_group_set(Object *obj, Visitor *v, const char * name,
>> +        void *opaque, Error **errp)
>> +
>> +{
>> +    ThrottleGroup *tg = THROTTLE_GROUP(obj);
>> +    ThrottleConfig cfg;
>> +    ThrottleParamInfo *info = opaque;
>> +    ThrottleLimits *arg = NULL;
>> +    Error *local_err = NULL;
>> +    int64_t value;
>> +
>> +    if (!info) {
>> +        arg = g_new0(ThrottleLimits, 1);
>> +        visit_type_ThrottleLimits(v, name, &arg, &local_err);
>> +        if (local_err) {
>> +            goto fail;
>> +        }
>> +        qemu_mutex_lock(&tg->lock);
>> +        throttle_get_config(&tg->ts, &cfg);
>> +        throttle_limits_to_config(arg, &cfg, &local_err);
>> +        if (local_err) {
>> +            qemu_mutex_unlock(&tg->lock);
>> +            goto fail;
>> +        }
>> +        throttle_config(&tg->ts, tg->clock_type, &cfg);
>> +        qemu_mutex_unlock(&tg->lock);
>> +
>> +        goto ret;
>> +    }
>
>There is no need to share a function between ThrottleLimits and
>individual fields.  The code would be clearer with two separate setter
>functions.  The individual fields removal patch will also be cleaner
>because it just needs to delete lines of code instead of modifying
>throttle_group_set().
>
>> +
>> +    /* If we have finished initialization, don't accept individual property
>> +     * changes through QOM. Throttle configuration limits must be set in one
>> +     * transaction, as certain combinations are invalid.
>> +     */
>> +    if (tg->is_initialized) {
>> +        error_setg(&local_err, "Property cannot be set after initialization");
>> +        goto fail;
>> +    }
>> +
>> +    visit_type_int64(v, name, &value, &local_err);
>> +    if (local_err) {
>> +        goto fail;
>> +    }
>> +    if (value < 0) {
>> +        error_setg(&local_err, "Property value must be in range "
>> +                               "[0, %"PRId64"]", INT64_MAX);
>
>Please print the maximum value relevant to the actual field type instead
>of INT64_MAX.

This checks the limits of the int64 field you give to QOM. I think you 
mean in the value assignment to each field that follows? In any case, 
since unsigned is the only smaller field we could convert it to 
uint64_t/uint32_t internally.

>
>> +        goto fail;
>> +    }
>> +
>> +    cfg = tg->ts.cfg;
>> +    switch (info->data_type) {
>> +    case UINT64:
>> +        {
>> +            uint64_t *field = (void *)&cfg.buckets[info->type] + info->offset;
>> +            *field = value;
>> +        }
>> +        break;
>> +    case DOUBLE:
>> +        {
>> +            double *field = (void *)&cfg.buckets[info->type] + info->offset;
>> +            *field = value;
>> +        }
>> +        break;
>> +    case UNSIGNED:
>> +        {
>> +            unsigned *field = (void *)&cfg.buckets[info->type] + info->offset;
>> +            *field = value;
>> +        }
>> +    }
>> +
>> +    tg->ts.cfg = cfg;
>> +    goto ret;
>> +
>> +fail:
>> +    error_propagate(errp, local_err);
>> +ret:
>> +    g_free(arg);
>> +    return;
>> +
>> +}
>> +
>> +static void throttle_group_get(Object *obj, Visitor *v, const char *name,
>> +        void *opaque, Error **errp)
>> +{
>> +    ThrottleGroup *tg = THROTTLE_GROUP(obj);
>> +    ThrottleConfig cfg;
>> +    ThrottleParamInfo *info = opaque;
>> +    ThrottleLimits *arg = NULL;
>> +    int64_t value;
>> +
>> +    if (!info) {
>> +        arg = g_new0(ThrottleLimits, 1);
>> +        qemu_mutex_lock(&tg->lock);
>> +        throttle_get_config(&tg->ts, &cfg);
>> +        qemu_mutex_unlock(&tg->lock);
>> +        throttle_config_to_throttle_limits(&cfg, arg);
>> +        visit_type_ThrottleLimits(v, name, &arg, errp);
>> +        g_free(arg);
>> +        return;
>> +    }
>> +
>> +    cfg = tg->ts.cfg;
>> +    switch (info->data_type) {
>> +    case UINT64:
>> +        {
>> +            uint64_t *field = (void *)&cfg.buckets[info->type] + info->offset;
>> +            value = *field;
>> +        }
>> +        break;
>> +    case DOUBLE:
>> +        {
>> +            double *field = (void *)&cfg.buckets[info->type] + info->offset;
>> +            value = *field;
>> +        }
>> +        break;
>> +    case UNSIGNED:
>> +        {
>> +            unsigned *field = (void *)&cfg.buckets[info->type] + info->offset;
>> +            value = *field;
>> +        }
>> +    }
>> +
>> +    visit_type_int64(v, name, &value, errp);
>> +}
>> +
>> +#undef THROTTLE_OPT_PREFIX
>> +#define THROTTLE_OPT_PREFIX "x-"
>> +static void throttle_group_obj_class_init(ObjectClass *klass, void *class_data)
>> +{
>> +    UserCreatableClass *ucc = USER_CREATABLE_CLASS(klass);
>> +
>> +    ucc->complete = throttle_group_obj_complete;
>> +    /* iops limits */
>> +    object_class_property_add(klass,
>> +                              THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_TOTAL,
>> +                              "int",
>> +                              throttle_group_get,
>> +                              throttle_group_set,
>> +                              NULL, &throttle_iops_total_info,
>> +                              &error_abort);
>> +    object_class_property_add(klass,
>> +                              THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_TOTAL_MAX,
>> +                              "int",
>> +                              throttle_group_get,
>> +                              throttle_group_set,
>> +                              NULL, &throttle_iops_total_max_info,
>> +                              &error_abort);
>> +    object_class_property_add(klass,
>> +                              THROTTLE_OPT_PREFIX
>> +                              QEMU_OPT_IOPS_TOTAL_MAX_LENGTH,
>> +                              "int",
>> +                              throttle_group_get,
>> +                              throttle_group_set,
>> +                              NULL, &throttle_iops_total_max_length_info,
>> +                              &error_abort);
>> +    object_class_property_add(klass,
>> +                              THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_READ,
>> +                              "int",
>> +                              throttle_group_get,
>> +                              throttle_group_set,
>> +                              NULL, &throttle_iops_read_info,
>> +                              &error_abort);
>> +    object_class_property_add(klass,
>> +                              THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_READ_MAX,
>> +                              "int",
>> +                              throttle_group_get,
>> +                              throttle_group_set,
>> +                              NULL, &throttle_iops_read_max_info,
>> +                              &error_abort);
>> +    object_class_property_add(klass,
>> +                              THROTTLE_OPT_PREFIX
>> +                              QEMU_OPT_IOPS_READ_MAX_LENGTH,
>> +                              "int",
>> +                              throttle_group_get,
>> +                              throttle_group_set,
>> +                              NULL, &throttle_iops_read_max_length_info,
>> +                              &error_abort);
>> +    object_class_property_add(klass,
>> +                              THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_WRITE,
>> +                              "int",
>> +                              throttle_group_get,
>> +                              throttle_group_set,
>> +                              NULL, &throttle_iops_write_info, &error_abort);
>> +    object_class_property_add(klass,
>> +                              THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_WRITE_MAX,
>> +                              "int",
>> +                              throttle_group_get,
>> +                              throttle_group_set,
>> +                              NULL, &throttle_iops_write_max_info,
>> +                              &error_abort);
>> +    object_class_property_add(klass,
>> +                              THROTTLE_OPT_PREFIX
>> +                              QEMU_OPT_IOPS_WRITE_MAX_LENGTH,
>> +                              "int",
>> +                              throttle_group_get,
>> +                              throttle_group_set,
>> +                              NULL, &throttle_iops_write_max_length_info,
>> +                              &error_abort);
>> +    /* bps limits */
>> +    object_class_property_add(klass,
>> +                              THROTTLE_OPT_PREFIX QEMU_OPT_BPS_TOTAL,
>> +                              "int",
>> +                              throttle_group_get,
>> +                              throttle_group_set,
>> +                              NULL, &throttle_bps_total_info,
>> +                              &error_abort);
>> +    object_class_property_add(klass,
>> +                              THROTTLE_OPT_PREFIX QEMU_OPT_BPS_TOTAL_MAX,
>> +                              "int",
>> +                              throttle_group_get,
>> +                              throttle_group_set,
>> +                              NULL, &throttle_bps_total_max_info,
>> +                              &error_abort);
>> +    object_class_property_add(klass,
>> +                              THROTTLE_OPT_PREFIX
>> +                              QEMU_OPT_BPS_TOTAL_MAX_LENGTH,
>> +                              "int",
>> +                              throttle_group_get,
>> +                              throttle_group_set,
>> +                              NULL, &throttle_bps_total_max_length_info,
>> +                              &error_abort);
>> +    object_class_property_add(klass,
>> +                              THROTTLE_OPT_PREFIX QEMU_OPT_BPS_READ,
>> +                              "int",
>> +                              throttle_group_get,
>> +                              throttle_group_set,
>> +                              NULL, &throttle_bps_read_info,
>> +                              &error_abort);
>> +    object_class_property_add(klass,
>> +                              THROTTLE_OPT_PREFIX QEMU_OPT_BPS_READ_MAX,
>> +                              "int",
>> +                              throttle_group_get,
>> +                              throttle_group_set,
>> +                              NULL, &throttle_bps_read_max_info,
>> +                              &error_abort);
>> +    object_class_property_add(klass,
>> +                              THROTTLE_OPT_PREFIX QEMU_OPT_BPS_READ_MAX_LENGTH,
>> +                              "int",
>> +                              throttle_group_get,
>> +                              throttle_group_set,
>> +                              NULL, &throttle_bps_read_max_length_info,
>> +                              &error_abort);
>> +    object_class_property_add(klass,
>> +                              THROTTLE_OPT_PREFIX QEMU_OPT_BPS_WRITE,
>> +                              "int",
>> +                              throttle_group_get,
>> +                              throttle_group_set,
>> +                              NULL, &throttle_bps_write_info,
>> +                              &error_abort);
>> +    object_class_property_add(klass,
>> +                              THROTTLE_OPT_PREFIX QEMU_OPT_BPS_WRITE_MAX,
>> +                              "int",
>> +                              throttle_group_get,
>> +                              throttle_group_set,
>> +                              NULL, &throttle_bps_write_max_info,
>> +                              &error_abort);
>> +    object_class_property_add(klass,
>> +                              THROTTLE_OPT_PREFIX
>> +                              QEMU_OPT_BPS_WRITE_MAX_LENGTH,
>> +                              "int",
>> +                              throttle_group_get,
>> +                              throttle_group_set,
>> +                              NULL, &throttle_bps_write_max_length_info,
>> +                              &error_abort);
>> +    /* rest */
>> +    object_class_property_add(klass,
>> +                              THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_SIZE,
>> +                              "int",
>> +                              throttle_group_get,
>> +                              throttle_group_set,
>> +                              NULL, &throttle_iops_size_info,
>> +                              &error_abort);
>> +
>> +    /* ThrottleLimits */
>> +    object_class_property_add(klass,
>> +                              "limits", "ThrottleLimits",
>> +                              throttle_group_get,
>> +                              throttle_group_set,
>> +                              NULL, NULL,
>> +                              &error_abort);
>> +}
>> +
>> +static const TypeInfo throttle_group_info = {
>> +   .name = TYPE_THROTTLE_GROUP,
>> +   .parent = TYPE_OBJECT,
>> +   .class_init = throttle_group_obj_class_init,
>> +   .instance_size = sizeof(ThrottleGroup),
>> +   .instance_init = throttle_group_obj_init,
>> +   .instance_finalize = throttle_group_obj_finalize,
>> +   .interfaces = (InterfaceInfo[]) {
>> +       { TYPE_USER_CREATABLE },
>> +       { }
>> +   },
>> +};
>> +
>>  static void throttle_groups_init(void)
>>  {
>>      qemu_mutex_init(&throttle_groups_lock);
>> +    type_register_static(&throttle_group_info);
>>  }
>>
>> -block_init(throttle_groups_init);
>> +type_init(throttle_groups_init);
>> diff --git a/include/block/throttle-groups.h b/include/block/throttle-groups.h
>> index a0f27cac63..096d05ef92 100644
>> --- a/include/block/throttle-groups.h
>> +++ b/include/block/throttle-groups.h
>> @@ -53,6 +53,9 @@ typedef struct ThrottleGroupMember {
>>
>>  } ThrottleGroupMember;
>>
>> +#define TYPE_THROTTLE_GROUP "throttling-group"
>> +#define THROTTLE_GROUP(obj) OBJECT_CHECK(ThrottleGroup, (obj), TYPE_THROTTLE_GROUP)
>> +
>>  const char *throttle_group_get_name(ThrottleGroupMember *tgm);
>>
>>  ThrottleState *throttle_group_incref(const char *name);
>> diff --git a/include/qemu/throttle-options.h b/include/qemu/throttle-options.h
>> index 3133d1ca40..182b7896e1 100644
>> --- a/include/qemu/throttle-options.h
>> +++ b/include/qemu/throttle-options.h
>> @@ -10,81 +10,102 @@
>>  #ifndef THROTTLE_OPTIONS_H
>>  #define THROTTLE_OPTIONS_H
>>
>> +#define QEMU_OPT_IOPS_TOTAL "iops-total"
>> +#define QEMU_OPT_IOPS_TOTAL_MAX "iops-total-max"
>> +#define QEMU_OPT_IOPS_TOTAL_MAX_LENGTH "iops-total-max-length"
>> +#define QEMU_OPT_IOPS_READ "iops-read"
>> +#define QEMU_OPT_IOPS_READ_MAX "iops-read-max"
>> +#define QEMU_OPT_IOPS_READ_MAX_LENGTH "iops-read-max-length"
>> +#define QEMU_OPT_IOPS_WRITE "iops-write"
>> +#define QEMU_OPT_IOPS_WRITE_MAX "iops-write-max"
>> +#define QEMU_OPT_IOPS_WRITE_MAX_LENGTH "iops-write-max-length"
>> +#define QEMU_OPT_BPS_TOTAL "bps-total"
>> +#define QEMU_OPT_BPS_TOTAL_MAX "bps-total-max"
>> +#define QEMU_OPT_BPS_TOTAL_MAX_LENGTH "bps-total-max-length"
>> +#define QEMU_OPT_BPS_READ "bps-read"
>> +#define QEMU_OPT_BPS_READ_MAX "bps-read-max"
>> +#define QEMU_OPT_BPS_READ_MAX_LENGTH "bps-read-max-length"
>> +#define QEMU_OPT_BPS_WRITE "bps-write"
>> +#define QEMU_OPT_BPS_WRITE_MAX "bps-write-max"
>> +#define QEMU_OPT_BPS_WRITE_MAX_LENGTH "bps-write-max-length"
>> +#define QEMU_OPT_IOPS_SIZE "iops-size"
>> +
>> +#define THROTTLE_OPT_PREFIX "throttling."
>>  #define THROTTLE_OPTS \
>>            { \
>> -            .name = "throttling.iops-total",\
>> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_TOTAL,\
>>              .type = QEMU_OPT_NUMBER,\
>>              .help = "limit total I/O operations per second",\
>>          },{ \
>> -            .name = "throttling.iops-read",\
>> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_READ,\
>>              .type = QEMU_OPT_NUMBER,\
>>              .help = "limit read operations per second",\
>>          },{ \
>> -            .name = "throttling.iops-write",\
>> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_WRITE,\
>>              .type = QEMU_OPT_NUMBER,\
>>              .help = "limit write operations per second",\
>>          },{ \
>> -            .name = "throttling.bps-total",\
>> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_BPS_TOTAL,\
>>              .type = QEMU_OPT_NUMBER,\
>>              .help = "limit total bytes per second",\
>>          },{ \
>> -            .name = "throttling.bps-read",\
>> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_BPS_READ,\
>>              .type = QEMU_OPT_NUMBER,\
>>              .help = "limit read bytes per second",\
>>          },{ \
>> -            .name = "throttling.bps-write",\
>> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_BPS_WRITE,\
>>              .type = QEMU_OPT_NUMBER,\
>>              .help = "limit write bytes per second",\
>>          },{ \
>> -            .name = "throttling.iops-total-max",\
>> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_TOTAL_MAX,\
>>              .type = QEMU_OPT_NUMBER,\
>>              .help = "I/O operations burst",\
>>          },{ \
>> -            .name = "throttling.iops-read-max",\
>> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_READ_MAX,\
>>              .type = QEMU_OPT_NUMBER,\
>>              .help = "I/O operations read burst",\
>>          },{ \
>> -            .name = "throttling.iops-write-max",\
>> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_WRITE_MAX,\
>>              .type = QEMU_OPT_NUMBER,\
>>              .help = "I/O operations write burst",\
>>          },{ \
>> -            .name = "throttling.bps-total-max",\
>> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_BPS_TOTAL_MAX,\
>>              .type = QEMU_OPT_NUMBER,\
>>              .help = "total bytes burst",\
>>          },{ \
>> -            .name = "throttling.bps-read-max",\
>> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_BPS_READ_MAX,\
>>              .type = QEMU_OPT_NUMBER,\
>>              .help = "total bytes read burst",\
>>          },{ \
>> -            .name = "throttling.bps-write-max",\
>> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_BPS_WRITE_MAX,\
>>              .type = QEMU_OPT_NUMBER,\
>>              .help = "total bytes write burst",\
>>          },{ \
>> -            .name = "throttling.iops-total-max-length",\
>> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_TOTAL_MAX_LENGTH,\
>>              .type = QEMU_OPT_NUMBER,\
>>              .help = "length of the iops-total-max burst period, in seconds",\
>>          },{ \
>> -            .name = "throttling.iops-read-max-length",\
>> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_READ_MAX_LENGTH,\
>>              .type = QEMU_OPT_NUMBER,\
>>              .help = "length of the iops-read-max burst period, in seconds",\
>>          },{ \
>> -            .name = "throttling.iops-write-max-length",\
>> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_WRITE_MAX_LENGTH,\
>>              .type = QEMU_OPT_NUMBER,\
>>              .help = "length of the iops-write-max burst period, in seconds",\
>>          },{ \
>> -            .name = "throttling.bps-total-max-length",\
>> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_BPS_TOTAL_MAX_LENGTH,\
>>              .type = QEMU_OPT_NUMBER,\
>>              .help = "length of the bps-total-max burst period, in seconds",\
>>          },{ \
>> -            .name = "throttling.bps-read-max-length",\
>> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_BPS_READ_MAX_LENGTH,\
>>              .type = QEMU_OPT_NUMBER,\
>>              .help = "length of the bps-read-max burst period, in seconds",\
>>          },{ \
>> -            .name = "throttling.bps-write-max-length",\
>> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_BPS_WRITE_MAX_LENGTH,\
>>              .type = QEMU_OPT_NUMBER,\
>>              .help = "length of the bps-write-max burst period, in seconds",\
>>          },{ \
>> -            .name = "throttling.iops-size",\
>> +            .name = THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_SIZE,\
>>              .type = QEMU_OPT_NUMBER,\
>>              .help = "when limiting by iops max size of an I/O in bytes",\
>>          }
>> diff --git a/include/qemu/throttle.h b/include/qemu/throttle.h
>> index d056008c18..17e750b12d 100644
>> --- a/include/qemu/throttle.h
>> +++ b/include/qemu/throttle.h
>> @@ -152,5 +152,8 @@ bool throttle_schedule_timer(ThrottleState *ts,
>>                               bool is_write);
>>
>>  void throttle_account(ThrottleState *ts, bool is_write, uint64_t size);
>> +void throttle_limits_to_config(ThrottleLimits *arg, ThrottleConfig *cfg,
>> +                               Error **errp);
>> +void throttle_config_to_throttle_limits(ThrottleConfig *cfg, ThrottleLimits *var);
>>
>>  #endif
>> diff --git a/qapi/block-core.json b/qapi/block-core.json
>> index c437aa50ef..1084158b6a 100644
>> --- a/qapi/block-core.json
>> +++ b/qapi/block-core.json
>> @@ -1896,6 +1896,51 @@
>>              '*iops_size': 'int', '*group': 'str' } }
>>
>>  ##
>> +# @ThrottleLimits:
>> +#
>> +# Limit parameters for throttling.
>
>Please add a note that atomic updates are recommended.  Even though
>fields are optional it's best to specify all fields one wishes to change
>at once instead of performing multiple changes.

Will do, thanks.
>
>> +#
>> +# @iops-total:             limit total I/O operations per second
>> +# @iops-total-max:         I/O operations burst
>> +# @iops-total-max-length:  length of the iops-total-max burst period, in seconds
>> +#                          It must only be set if @iops-total-max is set as well.
>> +# @iops-read:              limit read operations per second
>> +# @iops-read-max:          I/O operations read burst
>> +# @iops-read-max-length:   length of the iops-read-max burst period, in seconds
>> +#                          It must only be set if @iops-read-max is set as well.
>> +# @iops-write:             limit write operations per second
>> +# @iops-write-max:         I/O operations write burst
>> +# @iops-write-max-length:  length of the iops-write-max burst period, in seconds
>> +#                          It must only be set if @iops-write-max is set as well.
>> +# @bps-total:              limit total bytes per second
>> +# @bps-total-max:          total bytes burst
>> +# @bps-total-max-length:   length of the bps-total-max burst period, in seconds.
>> +#                          It must only be set if @bps-total-max is set as well.
>> +# @bps-read:               limit read bytes per second
>> +# @bps-read-max:           total bytes read burst
>> +# @bps-read-max-length:    length of the bps-read-max burst period, in seconds
>> +#                          It must only be set if @bps-read-max is set as well.
>> +# @bps-write:              limit write bytes per second
>> +# @bps-write-max:          total bytes write burst
>> +# @bps-write-max-length:   length of the bps-write-max burst period, in seconds
>> +#                          It must only be set if @bps-write-max is set as well.
>> +# @iops-size:              when limiting by iops max size of an I/O in bytes
>> +#
>> +# Since: 2.10
>> +##
>> +{ 'struct': 'ThrottleLimits',
>> +  'data': { '*iops-total' : 'int', '*iops-total-max' : 'int',
>> +            '*iops-total-max-length' : 'int', '*iops-read' : 'int',
>> +            '*iops-read-max' : 'int', '*iops-read-max-length' : 'int',
>> +            '*iops-write' : 'int', '*iops-write-max' : 'int',
>> +            '*iops-write-max-length' : 'int', '*bps-total' : 'int',
>> +            '*bps-total-max' : 'int', '*bps-total-max-length' : 'int',
>> +            '*bps-read' : 'int', '*bps-read-max' : 'int',
>> +            '*bps-read-max-length' : 'int', '*bps-write' : 'int',
>> +            '*bps-write-max' : 'int', '*bps-write-max-length' : 'int',
>> +            '*iops-size' : 'int' } }
>> +
>> +##
>>  # @block-stream:
>>  #
>>  # Copy data from a backing file into a block device.
>> diff --git a/tests/test-throttle.c b/tests/test-throttle.c
>> index 57cf5ba711..0ea9093eee 100644
>> --- a/tests/test-throttle.c
>> +++ b/tests/test-throttle.c
>> @@ -662,6 +662,7 @@ int main(int argc, char **argv)
>>      qemu_init_main_loop(&error_fatal);
>>      ctx = qemu_get_aio_context();
>>      bdrv_init();
>> +    module_call_init(MODULE_INIT_QOM);
>>
>>      do {} while (g_main_context_iteration(NULL, false));
>>
>> diff --git a/util/throttle.c b/util/throttle.c
>> index b2a52b8b34..3bb21a63e6 100644
>> --- a/util/throttle.c
>> +++ b/util/throttle.c
>> @@ -502,3 +502,124 @@ void throttle_account(ThrottleState *ts, bool is_write, uint64_t size)
>>      }
>>  }
>>
>> +/* return a ThrottleConfig based on the options in a ThrottleLimits
>> + *
>> + * @arg:    the ThrottleLimits object to read from
>> + * @cfg:    the ThrottleConfig to edit
>> + * @errp:   error object
>> + */
>> +void throttle_limits_to_config(ThrottleLimits *arg, ThrottleConfig *cfg,
>> +                               Error **errp)
>> +{
>> +    if (arg->has_bps_total) {
>> +        cfg->buckets[THROTTLE_BPS_TOTAL].avg = arg->bps_total;
>> +    }
>> +    if (arg->has_bps_read) {
>> +        cfg->buckets[THROTTLE_BPS_READ].avg  = arg->bps_read;
>> +    }
>> +    if (arg->has_bps_write) {
>> +        cfg->buckets[THROTTLE_BPS_WRITE].avg = arg->bps_write;
>> +    }
>> +
>> +    if (arg->has_iops_total) {
>> +        cfg->buckets[THROTTLE_OPS_TOTAL].avg = arg->iops_total;
>> +    }
>> +    if (arg->has_iops_read) {
>> +        cfg->buckets[THROTTLE_OPS_READ].avg  = arg->iops_read;
>> +    }
>> +    if (arg->has_iops_write) {
>> +        cfg->buckets[THROTTLE_OPS_WRITE].avg = arg->iops_write;
>> +    }
>> +
>> +    if (arg->has_bps_total_max) {
>> +        cfg->buckets[THROTTLE_BPS_TOTAL].max = arg->bps_total_max;
>> +    }
>> +    if (arg->has_bps_read_max) {
>> +        cfg->buckets[THROTTLE_BPS_READ].max = arg->bps_read_max;
>> +    }
>> +    if (arg->has_bps_write_max) {
>> +        cfg->buckets[THROTTLE_BPS_WRITE].max = arg->bps_write_max;
>> +    }
>> +    if (arg->has_iops_total_max) {
>> +        cfg->buckets[THROTTLE_OPS_TOTAL].max = arg->iops_total_max;
>> +    }
>> +    if (arg->has_iops_read_max) {
>> +        cfg->buckets[THROTTLE_OPS_READ].max = arg->iops_read_max;
>> +    }
>> +    if (arg->has_iops_write_max) {
>> +        cfg->buckets[THROTTLE_OPS_WRITE].max = arg->iops_write_max;
>> +    }
>> +
>> +    if (arg->has_bps_total_max_length) {
>> +        cfg->buckets[THROTTLE_BPS_TOTAL].burst_length = arg->bps_total_max_length;
>> +    }
>> +    if (arg->has_bps_read_max_length) {
>> +        cfg->buckets[THROTTLE_BPS_READ].burst_length = arg->bps_read_max_length;
>> +    }
>> +    if (arg->has_bps_write_max_length) {
>> +        cfg->buckets[THROTTLE_BPS_WRITE].burst_length = arg->bps_write_max_length;
>> +    }
>> +    if (arg->has_iops_total_max_length) {
>> +        cfg->buckets[THROTTLE_OPS_TOTAL].burst_length = arg->iops_total_max_length;
>> +    }
>> +    if (arg->has_iops_read_max_length) {
>> +        cfg->buckets[THROTTLE_OPS_READ].burst_length = arg->iops_read_max_length;
>> +    }
>> +    if (arg->has_iops_write_max_length) {
>> +        cfg->buckets[THROTTLE_OPS_WRITE].burst_length = arg->iops_write_max_length;
>> +    }
>> +
>> +    if (arg->has_iops_size) {
>> +        cfg->op_size = arg->iops_size;
>> +    }
>> +
>> +    throttle_is_valid(cfg, errp);
>> +}
>> +
>> +/* write the options of a ThrottleConfig to a ThrottleLimits
>> + *
>> + * @cfg:    the ThrottleConfig to read from
>> + * @var:    the ThrottleLimits to write to
>> + */
>> +void throttle_config_to_throttle_limits(ThrottleConfig *cfg, ThrottleLimits *var)
>> +{
>> +    var->bps_total               = cfg->buckets[THROTTLE_BPS_TOTAL].avg;
>> +    var->bps_read                = cfg->buckets[THROTTLE_BPS_READ].avg;
>> +    var->bps_write               = cfg->buckets[THROTTLE_BPS_WRITE].avg;
>> +    var->iops_total              = cfg->buckets[THROTTLE_OPS_TOTAL].avg;
>> +    var->iops_read               = cfg->buckets[THROTTLE_OPS_READ].avg;
>> +    var->iops_write              = cfg->buckets[THROTTLE_OPS_WRITE].avg;
>> +    var->bps_total_max           = cfg->buckets[THROTTLE_BPS_TOTAL].max;
>> +    var->bps_read_max            = cfg->buckets[THROTTLE_BPS_READ].max;
>> +    var->bps_write_max           = cfg->buckets[THROTTLE_BPS_WRITE].max;
>> +    var->iops_total_max          = cfg->buckets[THROTTLE_OPS_TOTAL].max;
>> +    var->iops_read_max           = cfg->buckets[THROTTLE_OPS_READ].max;
>> +    var->iops_write_max          = cfg->buckets[THROTTLE_OPS_WRITE].max;
>> +    var->bps_total_max_length    = cfg->buckets[THROTTLE_BPS_TOTAL].burst_length;
>> +    var->bps_read_max_length     = cfg->buckets[THROTTLE_BPS_READ].burst_length;
>> +    var->bps_write_max_length    = cfg->buckets[THROTTLE_BPS_WRITE].burst_length;
>> +    var->iops_total_max_length   = cfg->buckets[THROTTLE_OPS_TOTAL].burst_length;
>> +    var->iops_read_max_length    = cfg->buckets[THROTTLE_OPS_READ].burst_length;
>> +    var->iops_write_max_length   = cfg->buckets[THROTTLE_OPS_WRITE].burst_length;
>> +    var->iops_size               = cfg->op_size;
>> +
>> +    var->has_bps_total = true;
>> +    var->has_bps_read = true;
>> +    var->has_bps_write = true;
>> +    var->has_iops_total = true;
>> +    var->has_iops_read = true;
>> +    var->has_iops_write = true;
>> +    var->has_bps_total_max = true;
>> +    var->has_bps_read_max = true;
>> +    var->has_bps_write_max = true;
>> +    var->has_iops_total_max = true;
>> +    var->has_iops_read_max = true;
>> +    var->has_iops_write_max = true;
>> +    var->has_bps_read_max_length = true;
>> +    var->has_bps_total_max_length = true;
>> +    var->has_bps_write_max_length = true;
>> +    var->has_iops_total_max_length = true;
>> +    var->has_iops_read_max_length = true;
>> +    var->has_iops_write_max_length = true;
>> +    var->has_iops_size = true;
>> +}
>> --
>> 2.11.0
>>



[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [Qemu-devel] [Qemu-block] [PATCH 4/7] block: convert ThrottleGroup to object with QOM
  2017-07-25 10:29     ` Manos Pitsidianakis
@ 2017-07-25 16:09       ` Stefan Hajnoczi
  2017-07-25 16:21         ` Manos Pitsidianakis
  0 siblings, 1 reply; 17+ messages in thread
From: Stefan Hajnoczi @ 2017-07-25 16:09 UTC (permalink / raw)
  To: Manos Pitsidianakis, Stefan Hajnoczi, qemu-devel, Kevin Wolf,
	Alberto Garcia, qemu-block

[-- Attachment #1: Type: text/plain, Size: 2324 bytes --]

On Tue, Jul 25, 2017 at 01:29:08PM +0300, Manos Pitsidianakis wrote:
> On Mon, Jul 24, 2017 at 04:12:47PM +0100, Stefan Hajnoczi wrote:
> > On Fri, Jul 14, 2017 at 12:45:18PM +0300, Manos Pitsidianakis wrote:
> > > ThrottleGroup is converted to an object. This will allow the future
> > > throttle block filter drive easy creation and configuration of throttle
> > > groups in QMP and cli.
> > > 
> > > A new QAPI struct, ThrottleLimits, is introduced to provide a shared
> > > struct for all throttle configuration needs in QMP.
> > > 
> > > ThrottleGroups can be created via CLI as
> > >     -object throttling-group,id=foo,x-iops-total=100,x-..
> > 
> > Please make the QOM name and struct name consistent.  Either
> > ThrottleGroup/throttle-group or ThrottlingGroup/throttling-group but not
> > ThrottleGroup/throttling-group.
> 
> I did this on purpose because current throttling has ThrottleGroup
> internally and throttling.group in the command line. Should we keep this
> only in legacy and make it throttle-group everywhere?

I don't see a compatibility issue because -drive throttling.group= is a
-drive property while THROTTLE_GROUP ("throttling-group") is a QOM class
name.  They are unrelated and the QOM convention is for the
typedef/struct name (ThrottleGroup) to be consistent with the QOM class
name.

Therefore it should be safe to use "throttle-group" as the QOM class
name instead of "throttling-group".

> > > +    visit_type_int64(v, name, &value, &local_err);
> > > +    if (local_err) {
> > > +        goto fail;
> > > +    }
> > > +    if (value < 0) {
> > > +        error_setg(&local_err, "Property value must be in range "
> > > +                               "[0, %"PRId64"]", INT64_MAX);
> > 
> > Please print the maximum value relevant to the actual field type instead
> > of INT64_MAX.
> 
> This checks the limits of the int64 field you give to QOM. I think you mean
> in the value assignment to each field that follows? In any case, since
> unsigned is the only smaller field we could convert it to uint64_t/uint32_t
> internally.

I'm saying that UNSIGNED fields are silently truncated if the value is
larger than UINT_MAX, and also that the error message is misleading
since UNSIGNED fields cannot take values in the whole range we print.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 455 bytes --]

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [Qemu-devel] [Qemu-block] [PATCH 4/7] block: convert ThrottleGroup to object with QOM
  2017-07-25 16:09       ` [Qemu-devel] [Qemu-block] " Stefan Hajnoczi
@ 2017-07-25 16:21         ` Manos Pitsidianakis
  2017-07-26 11:40           ` Stefan Hajnoczi
  0 siblings, 1 reply; 17+ messages in thread
From: Manos Pitsidianakis @ 2017-07-25 16:21 UTC (permalink / raw)
  To: Stefan Hajnoczi
  Cc: Stefan Hajnoczi, qemu-devel, Kevin Wolf, Alberto Garcia, qemu-block

[-- Attachment #1: Type: text/plain, Size: 2778 bytes --]

On Tue, Jul 25, 2017 at 05:09:41PM +0100, Stefan Hajnoczi wrote:
>On Tue, Jul 25, 2017 at 01:29:08PM +0300, Manos Pitsidianakis wrote:
>> On Mon, Jul 24, 2017 at 04:12:47PM +0100, Stefan Hajnoczi wrote:
>> > On Fri, Jul 14, 2017 at 12:45:18PM +0300, Manos Pitsidianakis wrote:
>> > > ThrottleGroup is converted to an object. This will allow the future
>> > > throttle block filter drive easy creation and configuration of throttle
>> > > groups in QMP and cli.
>> > >
>> > > A new QAPI struct, ThrottleLimits, is introduced to provide a shared
>> > > struct for all throttle configuration needs in QMP.
>> > >
>> > > ThrottleGroups can be created via CLI as
>> > >     -object throttling-group,id=foo,x-iops-total=100,x-..
>> >
>> > Please make the QOM name and struct name consistent.  Either
>> > ThrottleGroup/throttle-group or ThrottlingGroup/throttling-group but not
>> > ThrottleGroup/throttling-group.
>>
>> I did this on purpose because current throttling has ThrottleGroup
>> internally and throttling.group in the command line. Should we keep this
>> only in legacy and make it throttle-group everywhere?
>
>I don't see a compatibility issue because -drive throttling.group= is a
>-drive property while THROTTLE_GROUP ("throttling-group") is a QOM class
>name.  They are unrelated and the QOM convention is for the
>typedef/struct name (ThrottleGroup) to be consistent with the QOM class
>name.
>
>Therefore it should be safe to use "throttle-group" as the QOM class
>name instead of "throttling-group".

I meant for consistency not compatibility. Otherwise it probably would 
be better to keep throttle-group/ThrottleGroup in the new interfaces.
>
>> > > +    visit_type_int64(v, name, &value, &local_err);
>> > > +    if (local_err) {
>> > > +        goto fail;
>> > > +    }
>> > > +    if (value < 0) {
>> > > +        error_setg(&local_err, "Property value must be in range "
>> > > +                               "[0, %"PRId64"]", INT64_MAX);
>> >
>> > Please print the maximum value relevant to the actual field type instead
>> > of INT64_MAX.
>>
>> This checks the limits of the int64 field you give to QOM. I think you mean
>> in the value assignment to each field that follows? In any case, since
>> unsigned is the only smaller field we could convert it to uint64_t/uint32_t
>> internally.
>
>I'm saying that UNSIGNED fields are silently truncated if the value is
>larger than UINT_MAX, and also that the error message is misleading
>since UNSIGNED fields cannot take values in the whole range we print.

Yes, wouldn't it be better to convert the unsigned field burst_length to 
uint64_t and take care of the overflow case? The field describes 
seconds, but there's no reason to keep it that small.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [Qemu-devel] [Qemu-block] [PATCH 4/7] block: convert ThrottleGroup to object with QOM
  2017-07-25 16:21         ` Manos Pitsidianakis
@ 2017-07-26 11:40           ` Stefan Hajnoczi
  0 siblings, 0 replies; 17+ messages in thread
From: Stefan Hajnoczi @ 2017-07-26 11:40 UTC (permalink / raw)
  To: Manos Pitsidianakis, Stefan Hajnoczi, qemu-devel, Kevin Wolf,
	Alberto Garcia, qemu-block

[-- Attachment #1: Type: text/plain, Size: 3311 bytes --]

On Tue, Jul 25, 2017 at 07:21:45PM +0300, Manos Pitsidianakis wrote:
> On Tue, Jul 25, 2017 at 05:09:41PM +0100, Stefan Hajnoczi wrote:
> > On Tue, Jul 25, 2017 at 01:29:08PM +0300, Manos Pitsidianakis wrote:
> > > On Mon, Jul 24, 2017 at 04:12:47PM +0100, Stefan Hajnoczi wrote:
> > > > On Fri, Jul 14, 2017 at 12:45:18PM +0300, Manos Pitsidianakis wrote:
> > > > > ThrottleGroup is converted to an object. This will allow the future
> > > > > throttle block filter drive easy creation and configuration of throttle
> > > > > groups in QMP and cli.
> > > > >
> > > > > A new QAPI struct, ThrottleLimits, is introduced to provide a shared
> > > > > struct for all throttle configuration needs in QMP.
> > > > >
> > > > > ThrottleGroups can be created via CLI as
> > > > >     -object throttling-group,id=foo,x-iops-total=100,x-..
> > > >
> > > > Please make the QOM name and struct name consistent.  Either
> > > > ThrottleGroup/throttle-group or ThrottlingGroup/throttling-group but not
> > > > ThrottleGroup/throttling-group.
> > > 
> > > I did this on purpose because current throttling has ThrottleGroup
> > > internally and throttling.group in the command line. Should we keep this
> > > only in legacy and make it throttle-group everywhere?
> > 
> > I don't see a compatibility issue because -drive throttling.group= is a
> > -drive property while THROTTLE_GROUP ("throttling-group") is a QOM class
> > name.  They are unrelated and the QOM convention is for the
> > typedef/struct name (ThrottleGroup) to be consistent with the QOM class
> > name.
> > 
> > Therefore it should be safe to use "throttle-group" as the QOM class
> > name instead of "throttling-group".
> 
> I meant for consistency not compatibility. Otherwise it probably would be
> better to keep throttle-group/ThrottleGroup in the new interfaces.

You could call it ThrottlingGroup ("throttling-group") for consistency.

> > 
> > > > > +    visit_type_int64(v, name, &value, &local_err);
> > > > > +    if (local_err) {
> > > > > +        goto fail;
> > > > > +    }
> > > > > +    if (value < 0) {
> > > > > +        error_setg(&local_err, "Property value must be in range "
> > > > > +                               "[0, %"PRId64"]", INT64_MAX);
> > > >
> > > > Please print the maximum value relevant to the actual field type instead
> > > > of INT64_MAX.
> > > 
> > > This checks the limits of the int64 field you give to QOM. I think you mean
> > > in the value assignment to each field that follows? In any case, since
> > > unsigned is the only smaller field we could convert it to uint64_t/uint32_t
> > > internally.
> > 
> > I'm saying that UNSIGNED fields are silently truncated if the value is
> > larger than UINT_MAX, and also that the error message is misleading
> > since UNSIGNED fields cannot take values in the whole range we print.
> 
> Yes, wouldn't it be better to convert the unsigned field burst_length to
> uint64_t and take care of the overflow case? The field describes seconds,
> but there's no reason to keep it that small.

A burst_length of UINT_MAX seconds is over 136 years.  I guess unsigned
int was chosen because larger values will never be used :).

That said, making burst_length uint64_t is fine from a compatibility
point of view.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 455 bytes --]

^ permalink raw reply	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2017-07-26 11:40 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-07-14  9:45 [Qemu-devel] [PATCH 0/7] add throttle block driver filter Manos Pitsidianakis
2017-07-14  9:45 ` [Qemu-devel] [PATCH 1/7] block: move ThrottleGroup membership to ThrottleGroupMember Manos Pitsidianakis
2017-07-24 14:36   ` Stefan Hajnoczi
2017-07-14  9:45 ` [Qemu-devel] [PATCH 2/7] block: add aio_context field in ThrottleGroupMember Manos Pitsidianakis
2017-07-24 14:38   ` Stefan Hajnoczi
2017-07-14  9:45 ` [Qemu-devel] [PATCH 3/7] block: tidy ThrottleGroupMember initializations Manos Pitsidianakis
2017-07-24 14:39   ` Stefan Hajnoczi
2017-07-14  9:45 ` [Qemu-devel] [PATCH 4/7] block: convert ThrottleGroup to object with QOM Manos Pitsidianakis
2017-07-24 15:12   ` Stefan Hajnoczi
2017-07-25 10:29     ` Manos Pitsidianakis
2017-07-25 16:09       ` [Qemu-devel] [Qemu-block] " Stefan Hajnoczi
2017-07-25 16:21         ` Manos Pitsidianakis
2017-07-26 11:40           ` Stefan Hajnoczi
2017-07-14  9:45 ` [Qemu-devel] [PATCH 5/7] block: add throttle block filter driver Manos Pitsidianakis
2017-07-18  9:21   ` [Qemu-devel] [Qemu-block] " Manos Pitsidianakis
2017-07-14  9:45 ` [Qemu-devel] [PATCH 6/7] block: add BlockDevOptionsThrottle to QAPI Manos Pitsidianakis
2017-07-14  9:45 ` [Qemu-devel] [PATCH 7/7] block: add throttle block filter driver interface tests Manos Pitsidianakis

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.