diff options
author | Peter Maydell | 2017-09-07 11:45:18 +0200 |
---|---|---|
committer | Peter Maydell | 2017-09-07 11:45:18 +0200 |
commit | 8ee5f9b3ecc94e3eb7a8235f4b2c3ec9024807f6 (patch) | |
tree | 302d6285c803b2317a41bcd1dc5ffc67c0959776 /block/throttle-groups.c | |
parent | Merge remote-tracking branch 'remotes/juanquintela/tags/tests/20170906' into ... (diff) | |
parent | qcow2: move qcow2_store_persistent_dirty_bitmaps() before cache flushing (diff) | |
download | qemu-8ee5f9b3ecc94e3eb7a8235f4b2c3ec9024807f6.tar.gz qemu-8ee5f9b3ecc94e3eb7a8235f4b2c3ec9024807f6.tar.xz qemu-8ee5f9b3ecc94e3eb7a8235f4b2c3ec9024807f6.zip |
Merge remote-tracking branch 'remotes/kevin/tags/for-upstream' into staging
Block layer patches
# gpg: Signature made Wed 06 Sep 2017 14:44:41 BST
# gpg: using RSA key 0x7F09B272C88F2FD6
# gpg: Good signature from "Kevin Wolf <kwolf@redhat.com>"
# Primary key fingerprint: DC3D EB15 9A9A F95D 3D74 56FE 7F09 B272 C88F 2FD6
* remotes/kevin/tags/for-upstream:
qcow2: move qcow2_store_persistent_dirty_bitmaps() before cache flushing
qemu-iotests: add 184 for throttle filter driver
block: add throttle block filter driver
block: convert ThrottleGroup to object with QOM
block: tidy ThrottleGroupMember initializations
block: add aio_context field in ThrottleGroupMember
block: move ThrottleGroup membership to ThrottleGroupMember
block: document semantics of bdrv_co_preadv|pwritev
qcow: Check failure of bdrv_getlength() and bdrv_truncate()
qcow: Change signature of get_cluster_offset()
block: add default implementations for bdrv_co_get_block_status()
block: remove bdrv_truncate callback in blkdebug
block: remove unused bdrv_media_changed
block: pass bdrv_* methods to bs->file by default in block filters
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Diffstat (limited to 'block/throttle-groups.c')
-rw-r--r-- | block/throttle-groups.c | 750 |
1 files changed, 558 insertions, 192 deletions
diff --git a/block/throttle-groups.c b/block/throttle-groups.c index 890bfded3f..6ba992c8d7 100644 --- a/block/throttle-groups.c +++ b/block/throttle-groups.c @@ -25,12 +25,20 @@ #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 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,82 +48,95 @@ * 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 { + 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 */ ThrottleState ts; - QLIST_HEAD(, BlockBackendPublic) head; - BlockBackend *tokens[2]; + QLIST_HEAD(, ThrottleGroupMember) head; + ThrottleGroupMember *tokens[2]; bool any_timer_armed[2]; QEMUClockType clock_type; - /* These two are protected by the global throttle_groups_lock */ - unsigned refcount; + /* This field is protected by the global QEMU mutex */ QTAILQ_ENTRY(ThrottleGroup) list; } ThrottleGroup; -static QemuMutex throttle_groups_lock; +/* This is protected by the global QEMU mutex */ static QTAILQ_HEAD(, ThrottleGroup) throttle_groups = QTAILQ_HEAD_INITIALIZER(throttle_groups); + +/* This function reads throttle_groups and must be called under the global + * mutex. + */ +static ThrottleGroup *throttle_group_by_name(const char *name) +{ + ThrottleGroup *iter; + + /* Look for an existing group with that name */ + QTAILQ_FOREACH(iter, &throttle_groups, list) { + if (!g_strcmp0(name, iter->name)) { + return iter; + } + } + + return NULL; +} + +/* This function reads throttle_groups and must be called under the global + * mutex. + */ +bool throttle_group_exists(const char *name) +{ + return throttle_group_by_name(name) != NULL; +} + /* Increments the reference count of a ThrottleGroup given its name. * * If no ThrottleGroup is found with the given name a new one is * created. * + * This function edits throttle_groups and must be called under the global + * mutex. + * * @name: the name of the ThrottleGroup * @ret: the ThrottleState member of the ThrottleGroup */ ThrottleState *throttle_group_incref(const char *name) { ThrottleGroup *tg = NULL; - ThrottleGroup *iter; - - 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; - } - } - - /* Create a new one if not found */ - if (!tg) { - tg = g_new0(ThrottleGroup, 1); + tg = throttle_group_by_name(name); + + if (tg) { + object_ref(OBJECT(tg)); + } else { + /* Create a new one if not found */ + /* 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(USER_CREATABLE(tg), &error_abort); } - tg->refcount++; - - qemu_mutex_unlock(&throttle_groups_lock); - return &tg->ts; } @@ -124,130 +145,123 @@ ThrottleState *throttle_group_incref(const char *name) * When the reference count reaches zero the ThrottleGroup is * destroyed. * + * This function edits throttle_groups and must be called under the global + * mutex. + * * @ts: The ThrottleGroup to unref, given by its ThrottleState member */ 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 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 +272,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 +303,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 +324,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,90 +342,86 @@ 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) { Coroutine *co; RestartData rd = { - .blk = blk, + .tgm = tgm, .is_write = 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_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 +429,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); @@ -455,13 +462,12 @@ void throttle_group_get_config(BlockBackend *blk, 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); - ThrottleState *ts = blkp->throttle_state; + 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 +476,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,85 +489,445 @@ 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 + * This function edits throttle_groups and must be called under the global + * mutex. + * + * @tgm: the ThrottleGroupMember to insert * @groupname: the name of the group + * @ctx: the AioContext to use */ -void throttle_group_register_blk(BlockBackend *blk, const char *groupname) +void throttle_group_register_tgm(ThrottleGroupMember *tgm, + const char *groupname, + AioContext *ctx) { int i; - BlockBackendPublic *blkp = blk_get_public(blk); ThrottleState *ts = throttle_group_incref(groupname); ThrottleGroup *tg = container_of(ts, ThrottleGroup, ts); - blkp->throttle_state = ts; + + tgm->throttle_state = ts; + tgm->aio_context = ctx; 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, - blk_get_aio_context(blk), + throttle_timers_init(&tgm->throttle_timers, + tgm->aio_context, tg->clock_type, read_timer_cb, write_timer_cb, - blk); + 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); } -/* 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])); + if (!ts) { + /* Discard already unregistered tgm */ + return; + } + + 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; +} + +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) +{ + ThrottleTimers *tt = &tgm->throttle_timers; + throttle_timers_detach_aio_context(tt); + tgm->aio_context = NULL; +} + +#undef THROTTLE_OPT_PREFIX +#define THROTTLE_OPT_PREFIX "x-" + +/* Helper struct and array for QOM property setter/getter */ +typedef struct { + const char *name; + BucketType type; + enum { + AVG, + MAX, + BURST_LENGTH, + IOPS_SIZE, + } category; +} ThrottleParamInfo; + +static ThrottleParamInfo properties[] = { + { + THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_TOTAL, + THROTTLE_OPS_TOTAL, AVG, + }, + { + THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_TOTAL_MAX, + THROTTLE_OPS_TOTAL, MAX, + }, + { + THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_TOTAL_MAX_LENGTH, + THROTTLE_OPS_TOTAL, BURST_LENGTH, + }, + { + THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_READ, + THROTTLE_OPS_READ, AVG, + }, + { + THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_READ_MAX, + THROTTLE_OPS_READ, MAX, + }, + { + THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_READ_MAX_LENGTH, + THROTTLE_OPS_READ, BURST_LENGTH, + }, + { + THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_WRITE, + THROTTLE_OPS_WRITE, AVG, + }, + { + THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_WRITE_MAX, + THROTTLE_OPS_WRITE, MAX, + }, + { + THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_WRITE_MAX_LENGTH, + THROTTLE_OPS_WRITE, BURST_LENGTH, + }, + { + THROTTLE_OPT_PREFIX QEMU_OPT_BPS_TOTAL, + THROTTLE_BPS_TOTAL, AVG, + }, + { + THROTTLE_OPT_PREFIX QEMU_OPT_BPS_TOTAL_MAX, + THROTTLE_BPS_TOTAL, MAX, + }, + { + THROTTLE_OPT_PREFIX QEMU_OPT_BPS_TOTAL_MAX_LENGTH, + THROTTLE_BPS_TOTAL, BURST_LENGTH, + }, + { + THROTTLE_OPT_PREFIX QEMU_OPT_BPS_READ, + THROTTLE_BPS_READ, AVG, + }, + { + THROTTLE_OPT_PREFIX QEMU_OPT_BPS_READ_MAX, + THROTTLE_BPS_READ, MAX, + }, + { + THROTTLE_OPT_PREFIX QEMU_OPT_BPS_READ_MAX_LENGTH, + THROTTLE_BPS_READ, BURST_LENGTH, + }, + { + THROTTLE_OPT_PREFIX QEMU_OPT_BPS_WRITE, + THROTTLE_BPS_WRITE, AVG, + }, + { + THROTTLE_OPT_PREFIX QEMU_OPT_BPS_WRITE_MAX, + THROTTLE_BPS_WRITE, MAX, + }, + { + THROTTLE_OPT_PREFIX QEMU_OPT_BPS_WRITE_MAX_LENGTH, + THROTTLE_BPS_WRITE, BURST_LENGTH, + }, + { + THROTTLE_OPT_PREFIX QEMU_OPT_IOPS_SIZE, + 0, IOPS_SIZE, + } +}; + +/* This function edits throttle_groups and must be called under the global + * mutex */ +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); } +/* This function edits throttle_groups and must be called under the global + * mutex */ +static void throttle_group_obj_complete(UserCreatable *obj, Error **errp) +{ + ThrottleGroup *tg = THROTTLE_GROUP(obj); + ThrottleConfig 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)); + } + /* We must have a group name at this point */ + assert(tg->name); + + /* error if name is duplicate */ + if (throttle_group_exists(tg->name)) { + error_setg(errp, "A group with this name already exists"); + return; + } + + /* check validity */ + throttle_get_config(&tg->ts, &cfg); + if (!throttle_is_valid(&cfg, errp)) { + return; + } + throttle_config(&tg->ts, tg->clock_type, &cfg); + QTAILQ_INSERT_TAIL(&throttle_groups, tg, list); + tg->is_initialized = true; +} + +/* This function edits throttle_groups and must be called under the global + * mutex */ +static void throttle_group_obj_finalize(Object *obj) +{ + ThrottleGroup *tg = THROTTLE_GROUP(obj); + if (tg->is_initialized) { + QTAILQ_REMOVE(&throttle_groups, tg, list); + } + 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; + Error *local_err = NULL; + int64_t value; + + /* 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 ret; + } + + visit_type_int64(v, name, &value, &local_err); + if (local_err) { + goto ret; + } + if (value < 0) { + error_setg(&local_err, "Property values cannot be negative"); + goto ret; + } + + cfg = &tg->ts.cfg; + switch (info->category) { + case AVG: + cfg->buckets[info->type].avg = value; + break; + case MAX: + cfg->buckets[info->type].max = value; + break; + case BURST_LENGTH: + if (value > UINT_MAX) { + error_setg(&local_err, "%s value must be in the" + "range [0, %u]", info->name, UINT_MAX); + goto ret; + } + cfg->buckets[info->type].burst_length = value; + break; + case IOPS_SIZE: + cfg->op_size = value; + break; + } + +ret: + error_propagate(errp, local_err); + 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; + int64_t value; + + throttle_get_config(&tg->ts, &cfg); + switch (info->category) { + case AVG: + value = cfg.buckets[info->type].avg; + break; + case MAX: + value = cfg.buckets[info->type].max; + break; + case BURST_LENGTH: + value = cfg.buckets[info->type].burst_length; + break; + case IOPS_SIZE: + value = cfg.op_size; + break; + } + + visit_type_int64(v, name, &value, errp); +} + +static void throttle_group_set_limits(Object *obj, Visitor *v, + const char *name, void *opaque, + Error **errp) + +{ + ThrottleGroup *tg = THROTTLE_GROUP(obj); + ThrottleConfig cfg; + ThrottleLimits arg = { 0 }; + ThrottleLimits *argp = &arg; + Error *local_err = NULL; + + visit_type_ThrottleLimits(v, name, &argp, &local_err); + if (local_err) { + goto ret; + } + qemu_mutex_lock(&tg->lock); + throttle_get_config(&tg->ts, &cfg); + throttle_limits_to_config(argp, &cfg, &local_err); + if (local_err) { + goto unlock; + } + throttle_config(&tg->ts, tg->clock_type, &cfg); + +unlock: + qemu_mutex_unlock(&tg->lock); +ret: + error_propagate(errp, local_err); + return; +} + +static void throttle_group_get_limits(Object *obj, Visitor *v, + const char *name, void *opaque, + Error **errp) +{ + ThrottleGroup *tg = THROTTLE_GROUP(obj); + ThrottleConfig cfg; + ThrottleLimits arg = { 0 }; + ThrottleLimits *argp = &arg; + + qemu_mutex_lock(&tg->lock); + throttle_get_config(&tg->ts, &cfg); + qemu_mutex_unlock(&tg->lock); + + throttle_config_to_limits(&cfg, argp); + + visit_type_ThrottleLimits(v, name, &argp, errp); +} + +static bool throttle_group_can_be_deleted(UserCreatable *uc) +{ + return OBJECT(uc)->ref == 1; +} + +static void throttle_group_obj_class_init(ObjectClass *klass, void *class_data) +{ + size_t i = 0; + UserCreatableClass *ucc = USER_CREATABLE_CLASS(klass); + + ucc->complete = throttle_group_obj_complete; + ucc->can_be_deleted = throttle_group_can_be_deleted; + + /* individual properties */ + for (i = 0; i < sizeof(properties) / sizeof(ThrottleParamInfo); i++) { + object_class_property_add(klass, + properties[i].name, + "int", + throttle_group_get, + throttle_group_set, + NULL, &properties[i], + &error_abort); + } + + /* ThrottleLimits */ + object_class_property_add(klass, + "limits", "ThrottleLimits", + throttle_group_get_limits, + throttle_group_set_limits, + 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); |