summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--block.c35
-rw-r--r--block/Makefile.objs1
-rw-r--r--block/blkdebug.c20
-rw-r--r--block/block-backend.c62
-rw-r--r--block/commit.c12
-rw-r--r--block/io.c26
-rw-r--r--block/mirror.c12
-rw-r--r--block/qapi.c8
-rw-r--r--block/qcow.c153
-rw-r--r--block/qcow2.c16
-rw-r--r--block/raw-format.c6
-rw-r--r--block/throttle-groups.c750
-rw-r--r--block/throttle.c237
-rw-r--r--blockdev.c4
-rw-r--r--include/block/block.h1
-rw-r--r--include/block/block_int.h25
-rw-r--r--include/block/throttle-groups.h52
-rw-r--r--include/qemu/throttle-options.h60
-rw-r--r--include/qemu/throttle.h3
-rw-r--r--include/sysemu/block-backend.h20
-rw-r--r--qapi/block-core.json66
-rwxr-xr-xtests/qemu-iotests/184205
-rw-r--r--tests/qemu-iotests/184.out302
-rw-r--r--tests/qemu-iotests/group1
-rw-r--r--tests/test-throttle.c111
-rw-r--r--util/throttle.c151
26 files changed, 1886 insertions, 453 deletions
diff --git a/block.c b/block.c
index b749bd6404..6dd47e414e 100644
--- a/block.c
+++ b/block.c
@@ -496,6 +496,8 @@ int bdrv_probe_blocksizes(BlockDriverState *bs, BlockSizes *bsz)
if (drv && drv->bdrv_probe_blocksizes) {
return drv->bdrv_probe_blocksizes(bs, bsz);
+ } else if (drv && drv->is_filter && bs->file) {
+ return bdrv_probe_blocksizes(bs->file->bs, bsz);
}
return -ENOTSUP;
@@ -513,6 +515,8 @@ int bdrv_probe_geometry(BlockDriverState *bs, HDGeometry *geo)
if (drv && drv->bdrv_probe_geometry) {
return drv->bdrv_probe_geometry(bs, geo);
+ } else if (drv && drv->is_filter && bs->file) {
+ return bdrv_probe_geometry(bs->file->bs, geo);
}
return -ENOTSUP;
@@ -3426,11 +3430,15 @@ int bdrv_truncate(BdrvChild *child, int64_t offset, PreallocMode prealloc,
assert(child->perm & BLK_PERM_RESIZE);
+ /* if bs->drv == NULL, bs is closed, so there's nothing to do here */
if (!drv) {
error_setg(errp, "No medium inserted");
return -ENOMEDIUM;
}
if (!drv->bdrv_truncate) {
+ if (bs->file && drv->is_filter) {
+ return bdrv_truncate(bs->file, offset, prealloc, errp);
+ }
error_setg(errp, "Image format driver does not support resize");
return -ENOTSUP;
}
@@ -3767,6 +3775,9 @@ int bdrv_has_zero_init(BlockDriverState *bs)
if (bs->drv->bdrv_has_zero_init) {
return bs->drv->bdrv_has_zero_init(bs);
}
+ if (bs->file && bs->drv->is_filter) {
+ return bdrv_has_zero_init(bs->file->bs);
+ }
/* safe default */
return 0;
@@ -3821,10 +3832,16 @@ void bdrv_get_backing_filename(BlockDriverState *bs,
int bdrv_get_info(BlockDriverState *bs, BlockDriverInfo *bdi)
{
BlockDriver *drv = bs->drv;
- if (!drv)
+ /* if bs->drv == NULL, bs is closed, so there's nothing to do here */
+ if (!drv) {
return -ENOMEDIUM;
- if (!drv->bdrv_get_info)
+ }
+ if (!drv->bdrv_get_info) {
+ if (bs->file && drv->is_filter) {
+ return bdrv_get_info(bs->file->bs, bdi);
+ }
return -ENOTSUP;
+ }
memset(bdi, 0, sizeof(*bdi));
return drv->bdrv_get_info(bs, bdi);
}
@@ -4175,20 +4192,6 @@ bool bdrv_is_inserted(BlockDriverState *bs)
}
/**
- * Return whether the media changed since the last call to this
- * function, or -ENOTSUP if we don't know. Most drivers don't know.
- */
-int bdrv_media_changed(BlockDriverState *bs)
-{
- BlockDriver *drv = bs->drv;
-
- if (drv && drv->bdrv_media_changed) {
- return drv->bdrv_media_changed(bs);
- }
- return -ENOTSUP;
-}
-
-/**
* If eject_flag is TRUE, eject the media. Otherwise, close the tray
*/
void bdrv_eject(BlockDriverState *bs, bool eject_flag)
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/blkdebug.c b/block/blkdebug.c
index 8e385acf54..46e53f2f09 100644
--- a/block/blkdebug.c
+++ b/block/blkdebug.c
@@ -628,16 +628,6 @@ static int coroutine_fn blkdebug_co_pdiscard(BlockDriverState *bs,
return bdrv_co_pdiscard(bs->file->bs, offset, bytes);
}
-static int64_t coroutine_fn blkdebug_co_get_block_status(
- BlockDriverState *bs, int64_t sector_num, int nb_sectors, int *pnum,
- BlockDriverState **file)
-{
- *pnum = nb_sectors;
- *file = bs->file->bs;
- return BDRV_BLOCK_RAW | BDRV_BLOCK_OFFSET_VALID |
- (sector_num << BDRV_SECTOR_BITS);
-}
-
static void blkdebug_close(BlockDriverState *bs)
{
BDRVBlkdebugState *s = bs->opaque;
@@ -808,12 +798,6 @@ static int64_t blkdebug_getlength(BlockDriverState *bs)
return bdrv_getlength(bs->file->bs);
}
-static int blkdebug_truncate(BlockDriverState *bs, int64_t offset,
- PreallocMode prealloc, Error **errp)
-{
- return bdrv_truncate(bs->file, offset, prealloc, errp);
-}
-
static void blkdebug_refresh_filename(BlockDriverState *bs, QDict *options)
{
BDRVBlkdebugState *s = bs->opaque;
@@ -896,6 +880,7 @@ static BlockDriver bdrv_blkdebug = {
.format_name = "blkdebug",
.protocol_name = "blkdebug",
.instance_size = sizeof(BDRVBlkdebugState),
+ .is_filter = true,
.bdrv_parse_filename = blkdebug_parse_filename,
.bdrv_file_open = blkdebug_open,
@@ -904,7 +889,6 @@ static BlockDriver bdrv_blkdebug = {
.bdrv_child_perm = bdrv_filter_default_perms,
.bdrv_getlength = blkdebug_getlength,
- .bdrv_truncate = blkdebug_truncate,
.bdrv_refresh_filename = blkdebug_refresh_filename,
.bdrv_refresh_limits = blkdebug_refresh_limits,
@@ -913,7 +897,7 @@ static BlockDriver bdrv_blkdebug = {
.bdrv_co_flush_to_disk = blkdebug_co_flush,
.bdrv_co_pwrite_zeroes = blkdebug_co_pwrite_zeroes,
.bdrv_co_pdiscard = blkdebug_co_pdiscard,
- .bdrv_co_get_block_status = blkdebug_co_get_block_status,
+ .bdrv_co_get_block_status = bdrv_co_get_block_status_from_file,
.bdrv_debug_event = blkdebug_debug_event,
.bdrv_debug_breakpoint = blkdebug_debug_breakpoint,
diff --git a/block/block-backend.c b/block/block-backend.c
index 1031742401..45d9101be3 100644
--- a/block/block-backend.c
+++ b/block/block-backend.c
@@ -273,9 +273,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.throttled_reqs_lock);
- qemu_co_queue_init(&blk->public.throttled_reqs[0]);
- qemu_co_queue_init(&blk->public.throttled_reqs[1]);
block_acct_init(&blk->stats);
notifier_list_init(&blk->remove_bs_notifiers);
@@ -343,7 +340,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) {
@@ -658,9 +655,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);
@@ -682,9 +682,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;
@@ -1046,8 +1047,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);
@@ -1070,10 +1072,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) {
@@ -1742,16 +1744,14 @@ static AioContext *blk_aiocb_get_aio_context(BlockAIOCB *acb)
void blk_set_aio_context(BlockBackend *blk, AioContext *new_context)
{
BlockDriverState *bs = blk_bs(blk);
+ ThrottleGroupMember *tgm = &blk->public.throttle_group_member;
if (bs) {
- if (blk->public.throttle_state) {
- throttle_timers_detach_aio_context(&blk->public.throttle_timers);
+ if (tgm->throttle_state) {
+ throttle_group_detach_aio_context(tgm);
+ throttle_group_attach_aio_context(tgm, new_context);
}
bdrv_set_aio_context(bs, new_context);
- if (blk->public.throttle_state) {
- throttle_timers_attach_aio_context(&blk->public.throttle_timers,
- new_context);
- }
}
}
@@ -1969,33 +1969,35 @@ 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, blk_get_aio_context(blk));
}
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;
}
@@ -2017,8 +2019,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);
}
}
@@ -2027,8 +2029,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/commit.c b/block/commit.c
index c7857c3321..898d91f653 100644
--- a/block/commit.c
+++ b/block/commit.c
@@ -244,16 +244,6 @@ static int coroutine_fn bdrv_commit_top_preadv(BlockDriverState *bs,
return bdrv_co_preadv(bs->backing, offset, bytes, qiov, flags);
}
-static int64_t coroutine_fn bdrv_commit_top_get_block_status(
- BlockDriverState *bs, int64_t sector_num, int nb_sectors, int *pnum,
- BlockDriverState **file)
-{
- *pnum = nb_sectors;
- *file = bs->backing->bs;
- return BDRV_BLOCK_RAW | BDRV_BLOCK_OFFSET_VALID |
- (sector_num << BDRV_SECTOR_BITS);
-}
-
static void bdrv_commit_top_refresh_filename(BlockDriverState *bs, QDict *opts)
{
bdrv_refresh_filename(bs->backing->bs);
@@ -279,7 +269,7 @@ static void bdrv_commit_top_child_perm(BlockDriverState *bs, BdrvChild *c,
static BlockDriver bdrv_commit_top = {
.format_name = "commit_top",
.bdrv_co_preadv = bdrv_commit_top_preadv,
- .bdrv_co_get_block_status = bdrv_commit_top_get_block_status,
+ .bdrv_co_get_block_status = bdrv_co_get_block_status_from_backing,
.bdrv_refresh_filename = bdrv_commit_top_refresh_filename,
.bdrv_close = bdrv_commit_top_close,
.bdrv_child_perm = bdrv_commit_top_child_perm,
diff --git a/block/io.c b/block/io.c
index 26003814eb..4378ae4c7d 100644
--- a/block/io.c
+++ b/block/io.c
@@ -1714,6 +1714,32 @@ typedef struct BdrvCoGetBlockStatusData {
bool done;
} BdrvCoGetBlockStatusData;
+int64_t coroutine_fn bdrv_co_get_block_status_from_file(BlockDriverState *bs,
+ int64_t sector_num,
+ int nb_sectors,
+ int *pnum,
+ BlockDriverState **file)
+{
+ assert(bs->file && bs->file->bs);
+ *pnum = nb_sectors;
+ *file = bs->file->bs;
+ return BDRV_BLOCK_RAW | BDRV_BLOCK_OFFSET_VALID |
+ (sector_num << BDRV_SECTOR_BITS);
+}
+
+int64_t coroutine_fn bdrv_co_get_block_status_from_backing(BlockDriverState *bs,
+ int64_t sector_num,
+ int nb_sectors,
+ int *pnum,
+ BlockDriverState **file)
+{
+ assert(bs->backing && bs->backing->bs);
+ *pnum = nb_sectors;
+ *file = bs->backing->bs;
+ return BDRV_BLOCK_RAW | BDRV_BLOCK_OFFSET_VALID |
+ (sector_num << BDRV_SECTOR_BITS);
+}
+
/*
* Returns the allocation status of the specified sectors.
* Drivers not implementing the functionality are assumed to not support
diff --git a/block/mirror.c b/block/mirror.c
index 429751b9fe..6531652d73 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -1059,16 +1059,6 @@ static int coroutine_fn bdrv_mirror_top_flush(BlockDriverState *bs)
return bdrv_co_flush(bs->backing->bs);
}
-static int64_t coroutine_fn bdrv_mirror_top_get_block_status(
- BlockDriverState *bs, int64_t sector_num, int nb_sectors, int *pnum,
- BlockDriverState **file)
-{
- *pnum = nb_sectors;
- *file = bs->backing->bs;
- return BDRV_BLOCK_RAW | BDRV_BLOCK_OFFSET_VALID |
- (sector_num << BDRV_SECTOR_BITS);
-}
-
static int coroutine_fn bdrv_mirror_top_pwrite_zeroes(BlockDriverState *bs,
int64_t offset, int bytes, BdrvRequestFlags flags)
{
@@ -1115,7 +1105,7 @@ static BlockDriver bdrv_mirror_top = {
.bdrv_co_pwrite_zeroes = bdrv_mirror_top_pwrite_zeroes,
.bdrv_co_pdiscard = bdrv_mirror_top_pdiscard,
.bdrv_co_flush = bdrv_mirror_top_flush,
- .bdrv_co_get_block_status = bdrv_mirror_top_get_block_status,
+ .bdrv_co_get_block_status = bdrv_co_get_block_status_from_backing,
.bdrv_refresh_filename = bdrv_mirror_top_refresh_filename,
.bdrv_close = bdrv_mirror_top_close,
.bdrv_child_perm = bdrv_mirror_top_child_perm,
diff --git a/block/qapi.c b/block/qapi.c
index 5f1a71f5d2..7fa2437923 100644
--- a/block/qapi.c
+++ b/block/qapi.c
@@ -66,10 +66,11 @@ BlockDeviceInfo *bdrv_block_device_info(BlockBackend *blk,
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;
@@ -117,7 +118,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/qcow.c b/block/qcow.c
index 63904a26ee..f450b00cfc 100644
--- a/block/qcow.c
+++ b/block/qcow.c
@@ -347,19 +347,22 @@ static int qcow_reopen_prepare(BDRVReopenState *state,
* 'compressed_size'. 'compressed_size' must be > 0 and <
* cluster_size
*
- * return 0 if not allocated.
+ * return 0 if not allocated, 1 if *result is assigned, and negative
+ * errno on failure.
*/
-static uint64_t get_cluster_offset(BlockDriverState *bs,
- uint64_t offset, int allocate,
- int compressed_size,
- int n_start, int n_end)
+static int get_cluster_offset(BlockDriverState *bs,
+ uint64_t offset, int allocate,
+ int compressed_size,
+ int n_start, int n_end, uint64_t *result)
{
BDRVQcowState *s = bs->opaque;
- int min_index, i, j, l1_index, l2_index;
- uint64_t l2_offset, *l2_table, cluster_offset, tmp;
+ int min_index, i, j, l1_index, l2_index, ret;
+ int64_t l2_offset;
+ uint64_t *l2_table, cluster_offset, tmp;
uint32_t min_count;
int new_l2_table;
+ *result = 0;
l1_index = offset >> (s->l2_bits + s->cluster_bits);
l2_offset = s->l1_table[l1_index];
new_l2_table = 0;
@@ -368,15 +371,20 @@ static uint64_t get_cluster_offset(BlockDriverState *bs,
return 0;
/* allocate a new l2 entry */
l2_offset = bdrv_getlength(bs->file->bs);
+ if (l2_offset < 0) {
+ return l2_offset;
+ }
/* round to cluster size */
- l2_offset = (l2_offset + s->cluster_size - 1) & ~(s->cluster_size - 1);
+ l2_offset = QEMU_ALIGN_UP(l2_offset, s->cluster_size);
/* update the L1 entry */
s->l1_table[l1_index] = l2_offset;
tmp = cpu_to_be64(l2_offset);
- if (bdrv_pwrite_sync(bs->file,
- s->l1_table_offset + l1_index * sizeof(tmp),
- &tmp, sizeof(tmp)) < 0)
- return 0;
+ ret = bdrv_pwrite_sync(bs->file,
+ s->l1_table_offset + l1_index * sizeof(tmp),
+ &tmp, sizeof(tmp));
+ if (ret < 0) {
+ return ret;
+ }
new_l2_table = 1;
}
for(i = 0; i < L2_CACHE_SIZE; i++) {
@@ -403,14 +411,17 @@ static uint64_t get_cluster_offset(BlockDriverState *bs,
l2_table = s->l2_cache + (min_index << s->l2_bits);
if (new_l2_table) {
memset(l2_table, 0, s->l2_size * sizeof(uint64_t));
- if (bdrv_pwrite_sync(bs->file, l2_offset, l2_table,
- s->l2_size * sizeof(uint64_t)) < 0)
- return 0;
+ ret = bdrv_pwrite_sync(bs->file, l2_offset, l2_table,
+ s->l2_size * sizeof(uint64_t));
+ if (ret < 0) {
+ return ret;
+ }
} else {
- if (bdrv_pread(bs->file, l2_offset, l2_table,
- s->l2_size * sizeof(uint64_t)) !=
- s->l2_size * sizeof(uint64_t))
- return 0;
+ ret = bdrv_pread(bs->file, l2_offset, l2_table,
+ s->l2_size * sizeof(uint64_t));
+ if (ret < 0) {
+ return ret;
+ }
}
s->l2_cache_offsets[min_index] = l2_offset;
s->l2_cache_counts[min_index] = 1;
@@ -427,24 +438,36 @@ static uint64_t get_cluster_offset(BlockDriverState *bs,
/* if the cluster is already compressed, we must
decompress it in the case it is not completely
overwritten */
- if (decompress_cluster(bs, cluster_offset) < 0)
- return 0;
+ if (decompress_cluster(bs, cluster_offset) < 0) {
+ return -EIO;
+ }
cluster_offset = bdrv_getlength(bs->file->bs);
- cluster_offset = (cluster_offset + s->cluster_size - 1) &
- ~(s->cluster_size - 1);
+ if ((int64_t) cluster_offset < 0) {
+ return cluster_offset;
+ }
+ cluster_offset = QEMU_ALIGN_UP(cluster_offset, s->cluster_size);
/* write the cluster content */
- if (bdrv_pwrite(bs->file, cluster_offset, s->cluster_cache,
- s->cluster_size) !=
- s->cluster_size)
- return -1;
+ ret = bdrv_pwrite(bs->file, cluster_offset, s->cluster_cache,
+ s->cluster_size);
+ if (ret < 0) {
+ return ret;
+ }
} else {
cluster_offset = bdrv_getlength(bs->file->bs);
+ if ((int64_t) cluster_offset < 0) {
+ return cluster_offset;
+ }
if (allocate == 1) {
/* round to cluster size */
- cluster_offset = (cluster_offset + s->cluster_size - 1) &
- ~(s->cluster_size - 1);
- bdrv_truncate(bs->file, cluster_offset + s->cluster_size,
- PREALLOC_MODE_OFF, NULL);
+ cluster_offset = QEMU_ALIGN_UP(cluster_offset, s->cluster_size);
+ if (cluster_offset + s->cluster_size > INT64_MAX) {
+ return -E2BIG;
+ }
+ ret = bdrv_truncate(bs->file, cluster_offset + s->cluster_size,
+ PREALLOC_MODE_OFF, NULL);
+ if (ret < 0) {
+ return ret;
+ }
/* if encrypted, we must initialize the cluster
content which won't be written */
if (bs->encrypted &&
@@ -459,13 +482,14 @@ static uint64_t get_cluster_offset(BlockDriverState *bs,
s->cluster_data,
BDRV_SECTOR_SIZE,
NULL) < 0) {
- errno = EIO;
- return -1;
+ return -EIO;
+ }
+ ret = bdrv_pwrite(bs->file,
+ cluster_offset + i * 512,
+ s->cluster_data, 512);
+ if (ret < 0) {
+ return ret;
}
- if (bdrv_pwrite(bs->file,
- cluster_offset + i * 512,
- s->cluster_data, 512) != 512)
- return -1;
}
}
}
@@ -477,23 +501,29 @@ static uint64_t get_cluster_offset(BlockDriverState *bs,
/* update L2 table */
tmp = cpu_to_be64(cluster_offset);
l2_table[l2_index] = tmp;
- if (bdrv_pwrite_sync(bs->file, l2_offset + l2_index * sizeof(tmp),
- &tmp, sizeof(tmp)) < 0)
- return 0;
+ ret = bdrv_pwrite_sync(bs->file, l2_offset + l2_index * sizeof(tmp),
+ &tmp, sizeof(tmp));
+ if (ret < 0) {
+ return ret;
+ }
}
- return cluster_offset;
+ *result = cluster_offset;
+ return 1;
}
static int64_t coroutine_fn qcow_co_get_block_status(BlockDriverState *bs,
int64_t sector_num, int nb_sectors, int *pnum, BlockDriverState **file)
{
BDRVQcowState *s = bs->opaque;
- int index_in_cluster, n;
+ int index_in_cluster, n, ret;
uint64_t cluster_offset;
qemu_co_mutex_lock(&s->lock);
- cluster_offset = get_cluster_offset(bs, sector_num << 9, 0, 0, 0, 0);
+ ret = get_cluster_offset(bs, sector_num << 9, 0, 0, 0, 0, &cluster_offset);
qemu_co_mutex_unlock(&s->lock);
+ if (ret < 0) {
+ return ret;
+ }
index_in_cluster = sector_num & (s->cluster_sectors - 1);
n = s->cluster_sectors - index_in_cluster;
if (n > nb_sectors)
@@ -585,8 +615,11 @@ static coroutine_fn int qcow_co_readv(BlockDriverState *bs, int64_t sector_num,
while (nb_sectors != 0) {
/* prepare next request */
- cluster_offset = get_cluster_offset(bs, sector_num << 9,
- 0, 0, 0, 0);
+ ret = get_cluster_offset(bs, sector_num << 9,
+ 0, 0, 0, 0, &cluster_offset);
+ if (ret < 0) {
+ break;
+ }
index_in_cluster = sector_num & (s->cluster_sectors - 1);
n = s->cluster_sectors - index_in_cluster;
if (n > nb_sectors) {
@@ -603,7 +636,7 @@ static coroutine_fn int qcow_co_readv(BlockDriverState *bs, int64_t sector_num,
ret = bdrv_co_readv(bs->backing, sector_num, n, &hd_qiov);
qemu_co_mutex_lock(&s->lock);
if (ret < 0) {
- goto fail;
+ break;
}
} else {
/* Note: in this case, no need to wait */
@@ -612,13 +645,15 @@ static coroutine_fn int qcow_co_readv(BlockDriverState *bs, int64_t sector_num,
} else if (cluster_offset & QCOW_OFLAG_COMPRESSED) {
/* add AIO support for compressed blocks ? */
if (decompress_cluster(bs, cluster_offset) < 0) {
- goto fail;
+ ret = -EIO;
+ break;
}
memcpy(buf,
s->cluster_cache + index_in_cluster * 512, 512 * n);
} else {
if ((cluster_offset & 511) != 0) {
- goto fail;
+ ret = -EIO;
+ break;
}
hd_iov.iov_base = (void *)buf;
hd_iov.iov_len = n * 512;
@@ -635,7 +670,8 @@ static coroutine_fn int qcow_co_readv(BlockDriverState *bs, int64_t sector_num,
assert(s->crypto);
if (qcrypto_block_decrypt(s->crypto, sector_num, buf,
n * BDRV_SECTOR_SIZE, NULL) < 0) {
- goto fail;
+ ret = -EIO;
+ break;
}
}
}
@@ -646,7 +682,6 @@ static coroutine_fn int qcow_co_readv(BlockDriverState *bs, int64_t sector_num,
buf += n * 512;
}
-done:
qemu_co_mutex_unlock(&s->lock);
if (qiov->niov > 1) {
@@ -655,10 +690,6 @@ done:
}
return ret;
-
-fail:
- ret = -EIO;
- goto done;
}
static coroutine_fn int qcow_co_writev(BlockDriverState *bs, int64_t sector_num,
@@ -697,9 +728,12 @@ static coroutine_fn int qcow_co_writev(BlockDriverState *bs, int64_t sector_num,
if (n > nb_sectors) {
n = nb_sectors;
}
- cluster_offset = get_cluster_offset(bs, sector_num << 9, 1, 0,
- index_in_cluster,
- index_in_cluster + n);
+ ret = get_cluster_offset(bs, sector_num << 9, 1, 0,
+ index_in_cluster,
+ index_in_cluster + n, &cluster_offset);
+ if (ret < 0) {
+ break;
+ }
if (!cluster_offset || (cluster_offset & 511) != 0) {
ret = -EIO;
break;
@@ -995,8 +1029,11 @@ qcow_co_pwritev_compressed(BlockDriverState *bs, uint64_t offset,
goto success;
}
qemu_co_mutex_lock(&s->lock);
- cluster_offset = get_cluster_offset(bs, offset, 2, out_len, 0, 0);
+ ret = get_cluster_offset(bs, offset, 2, out_len, 0, 0, &cluster_offset);
qemu_co_mutex_unlock(&s->lock);
+ if (ret < 0) {
+ goto fail;
+ }
if (cluster_offset == 0) {
ret = -EIO;
goto fail;
diff --git a/block/qcow2.c b/block/qcow2.c
index 2ec399663e..bae5893327 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -2036,6 +2036,14 @@ static int qcow2_inactivate(BlockDriverState *bs)
int ret, result = 0;
Error *local_err = NULL;
+ qcow2_store_persistent_dirty_bitmaps(bs, &local_err);
+ if (local_err != NULL) {
+ result = -EINVAL;
+ error_report_err(local_err);
+ error_report("Persistent bitmaps are lost for node '%s'",
+ bdrv_get_device_or_node_name(bs));
+ }
+
ret = qcow2_cache_flush(bs, s->l2_table_cache);
if (ret) {
result = ret;
@@ -2050,14 +2058,6 @@ static int qcow2_inactivate(BlockDriverState *bs)
strerror(-ret));
}
- qcow2_store_persistent_dirty_bitmaps(bs, &local_err);
- if (local_err != NULL) {
- result = -EINVAL;
- error_report_err(local_err);
- error_report("Persistent bitmaps are lost for node '%s'",
- bdrv_get_device_or_node_name(bs));
- }
-
if (result == 0) {
qcow2_mark_clean(bs);
}
diff --git a/block/raw-format.c b/block/raw-format.c
index 142649ed56..ab552c0954 100644
--- a/block/raw-format.c
+++ b/block/raw-format.c
@@ -372,11 +372,6 @@ static int raw_truncate(BlockDriverState *bs, int64_t offset,
return bdrv_truncate(bs->file, offset, prealloc, errp);
}
-static int raw_media_changed(BlockDriverState *bs)
-{
- return bdrv_media_changed(bs->file->bs);
-}
-
static void raw_eject(BlockDriverState *bs, bool eject_flag)
{
bdrv_eject(bs->file->bs, eject_flag);
@@ -510,7 +505,6 @@ BlockDriver bdrv_raw = {
.bdrv_refresh_limits = &raw_refresh_limits,
.bdrv_probe_blocksizes = &raw_probe_blocksizes,
.bdrv_probe_geometry = &raw_probe_geometry,
- .bdrv_media_changed = &raw_media_changed,
.bdrv_eject = &raw_eject,
.bdrv_lock_medium = &raw_lock_medium,
.bdrv_co_ioctl = &raw_co_ioctl,
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);
diff --git a/block/throttle.c b/block/throttle.c
new file mode 100644
index 0000000000..5bca76300f
--- /dev/null
+++ b/block/throttle.c
@@ -0,0 +1,237 @@
+/*
+ * 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"
+
+static QemuOptsList throttle_opts = {
+ .name = "throttle",
+ .head = QTAILQ_HEAD_INITIALIZER(throttle_opts.head),
+ .desc = {
+ {
+ .name = QEMU_OPT_THROTTLE_GROUP_NAME,
+ .type = QEMU_OPT_STRING,
+ .help = "Name of the throttle group",
+ },
+ { /* end of list */ }
+ },
+};
+
+static int throttle_configure_tgm(BlockDriverState *bs,
+ ThrottleGroupMember *tgm,
+ QDict *options, Error **errp)
+{
+ int ret;
+ const char *group_name;
+ Error *local_err = NULL;
+ QemuOpts *opts = qemu_opts_create(&throttle_opts, NULL, 0, &error_abort);
+
+ qemu_opts_absorb_qdict(opts, options, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ ret = -EINVAL;
+ goto fin;
+ }
+
+ group_name = qemu_opt_get(opts, QEMU_OPT_THROTTLE_GROUP_NAME);
+ if (!group_name) {
+ error_setg(errp, "Please specify a throttle group");
+ ret = -EINVAL;
+ goto fin;
+ } else if (!throttle_group_exists(group_name)) {
+ error_setg(errp, "Throttle group '%s' does not exist", group_name);
+ ret = -EINVAL;
+ goto fin;
+ }
+
+ /* Register membership to group with name group_name */
+ throttle_group_register_tgm(tgm, group_name, bdrv_get_aio_context(bs));
+ ret = 0;
+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;
+ }
+ bs->supported_write_flags = bs->file->bs->supported_write_flags;
+ bs->supported_zero_flags = bs->file->bs->supported_zero_flags;
+
+ 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_pwritev(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;
+
+ 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 *reopen_state)
+{
+ ThrottleGroupMember *old_tgm = reopen_state->bs->opaque;
+ ThrottleGroupMember *new_tgm = reopen_state->opaque;
+
+ throttle_group_unregister_tgm(old_tgm);
+ g_free(old_tgm);
+ reopen_state->bs->opaque = new_tgm;
+ reopen_state->opaque = NULL;
+}
+
+static void throttle_reopen_abort(BDRVReopenState *reopen_state)
+{
+ ThrottleGroupMember *tgm = reopen_state->opaque;
+
+ throttle_group_unregister_tgm(tgm);
+ g_free(tgm);
+ reopen_state->opaque = NULL;
+}
+
+static bool throttle_recurse_is_first_non_filter(BlockDriverState *bs,
+ BlockDriverState *candidate)
+{
+ return bdrv_recurse_is_first_non_filter(bs->file->bs, candidate);
+}
+
+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 = throttle_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,
+ .bdrv_co_get_block_status = bdrv_co_get_block_status_from_file,
+
+ .is_filter = true,
+};
+
+static void bdrv_throttle_init(void)
+{
+ bdrv_register(&bdrv_throttle);
+}
+
+block_init(bdrv_throttle_init);
diff --git a/blockdev.c b/blockdev.c
index 796beaed94..56a6b24a0b 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -2686,7 +2686,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 :
@@ -2696,7 +2696,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/block.h b/include/block/block.h
index ab80195378..2ad18775af 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -441,7 +441,6 @@ int bdrv_can_set_read_only(BlockDriverState *bs, bool read_only,
int bdrv_set_read_only(BlockDriverState *bs, bool read_only, Error **errp);
bool bdrv_is_sg(BlockDriverState *bs);
bool bdrv_is_inserted(BlockDriverState *bs);
-int bdrv_media_changed(BlockDriverState *bs);
void bdrv_lock_medium(BlockDriverState *bs, bool locked);
void bdrv_eject(BlockDriverState *bs, bool eject_flag);
const char *bdrv_get_format_name(BlockDriverState *bs);
diff --git a/include/block/block_int.h b/include/block/block_int.h
index 7816b43a27..ba4c383393 100644
--- a/include/block/block_int.h
+++ b/include/block/block_int.h
@@ -87,7 +87,11 @@ struct BlockDriver {
const char *format_name;
int instance_size;
- /* set to true if the BlockDriver is a block filter */
+ /* set to true if the BlockDriver is a block filter. Block filters pass
+ * certain callbacks that refer to data (see block.c) to their bs->file if
+ * the driver doesn't implement them. Drivers that do not wish to forward
+ * must implement them and return -ENOTSUP.
+ */
bool is_filter;
/* for snapshots block filter like Quorum can implement the
* following recursive callback.
@@ -275,7 +279,6 @@ struct BlockDriver {
/* removable device specific */
bool (*bdrv_is_inserted)(BlockDriverState *bs);
- int (*bdrv_media_changed)(BlockDriverState *bs);
void (*bdrv_eject)(BlockDriverState *bs, bool eject_flag);
void (*bdrv_lock_medium)(BlockDriverState *bs, bool locked);
@@ -992,6 +995,24 @@ void bdrv_format_default_perms(BlockDriverState *bs, BdrvChild *c,
uint64_t perm, uint64_t shared,
uint64_t *nperm, uint64_t *nshared);
+/*
+ * Default implementation for drivers to pass bdrv_co_get_block_status() to
+ * their file.
+ */
+int64_t coroutine_fn bdrv_co_get_block_status_from_file(BlockDriverState *bs,
+ int64_t sector_num,
+ int nb_sectors,
+ int *pnum,
+ BlockDriverState **file);
+/*
+ * Default implementation for drivers to pass bdrv_co_get_block_status() to
+ * their backing file.
+ */
+int64_t coroutine_fn bdrv_co_get_block_status_from_backing(BlockDriverState *bs,
+ int64_t sector_num,
+ int nb_sectors,
+ int *pnum,
+ BlockDriverState **file);
const char *bdrv_get_parent_name(const BlockDriverState *bs);
void blk_dev_change_media_cb(BlockBackend *blk, bool load, Error **errp);
bool blk_dev_has_removable_media(BlockBackend *blk);
diff --git a/include/block/throttle-groups.h b/include/block/throttle-groups.h
index d983d34074..e2fd0513c4 100644
--- a/include/block/throttle-groups.h
+++ b/include/block/throttle-groups.h
@@ -28,20 +28,58 @@
#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 {
+ AioContext *aio_context;
+ /* 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;
+
+#define TYPE_THROTTLE_GROUP "throttle-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);
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,
+ 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(BlockBackend *blk,
+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);
+/*
+ * throttle_group_exists() must be called under the global
+ * mutex.
+ */
+bool throttle_group_exists(const char *name);
#endif
diff --git a/include/qemu/throttle-options.h b/include/qemu/throttle-options.h
index 3133d1ca40..3528a8f4a2 100644
--- a/include/qemu/throttle-options.h
+++ b/include/qemu/throttle-options.h
@@ -10,81 +10,103 @@
#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 QEMU_OPT_THROTTLE_GROUP_NAME "throttle-group"
+
+#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 8e01885d29..8c93237866 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_limits(ThrottleConfig *cfg, ThrottleLimits *var);
#endif
diff --git a/include/sysemu/block-backend.h b/include/sysemu/block-backend.h
index aadc733daf..c4e52a5fa3 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/qapi/block-core.json b/qapi/block-core.json
index 28abb9e6cf..bb11815608 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -1906,6 +1906,54 @@
'*iops_size': 'int', '*group': 'str' } }
##
+# @ThrottleLimits:
+#
+# Limit parameters for throttling.
+# Since some limit combinations are illegal, limits should always be set in one
+# transaction. All fields are optional. When setting limits, if a field is
+# missing the current value is not changed.
+#
+# @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.11
+##
+{ '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.
@@ -2175,6 +2223,7 @@
# Drivers that are supported in block device operations.
#
# @vxhs: Since 2.10
+# @throttle: Since 2.11
#
# Since: 2.9
##
@@ -2184,7 +2233,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:
@@ -3048,6 +3097,20 @@
'*tls-creds': 'str' } }
##
+# @BlockdevOptionsThrottle:
+#
+# Driver specific block device options for the throttle driver
+#
+# @throttle-group: the name of the throttle-group object to use. It
+# must already exist.
+# @file: reference to or definition of the data source block device
+# Since: 2.11
+##
+{ 'struct': 'BlockdevOptionsThrottle',
+ 'data': { 'throttle-group': 'str',
+ 'file' : 'BlockdevRef'
+ } }
+##
# @BlockdevOptions:
#
# Options for creating a block device. Many options are available for all
@@ -3108,6 +3171,7 @@
'replication':'BlockdevOptionsReplication',
'sheepdog': 'BlockdevOptionsSheepdog',
'ssh': 'BlockdevOptionsSsh',
+ 'throttle': 'BlockdevOptionsThrottle',
'vdi': 'BlockdevOptionsGenericFormat',
'vhdx': 'BlockdevOptionsGenericFormat',
'vmdk': 'BlockdevOptionsGenericCOWFormat',
diff --git a/tests/qemu-iotests/184 b/tests/qemu-iotests/184
new file mode 100755
index 0000000000..704f38f936
--- /dev/null
+++ b/tests/qemu-iotests/184
@@ -0,0 +1,205 @@
+#!/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 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": "throttle-group",
+ "id": "group0",
+ "props": {
+ "limits" : {
+ "iops-total": 1000
+ }
+ }
+ }
+}
+{ "execute": "blockdev-add",
+ "arguments": {
+ "driver": "throttle",
+ "node-name": "throttle0",
+ "throttle-group": "group0",
+ "file": "disk0"
+ }
+}
+{ "execute": "query-named-block-nodes" }
+{ "execute": "query-block" }
+{ "execute": "quit" }
+EOF
+
+echo
+echo "== property changes in ThrottleGroup =="
+
+run_qemu <<EOF
+{ "execute": "qmp_capabilities" }
+{ "execute": "object-add",
+ "arguments": {
+ "qom-type": "throttle-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": "quit" }
+EOF
+
+echo
+echo "== object creation/set errors =="
+
+run_qemu <<EOF
+{ "execute": "qmp_capabilities" }
+{ "execute": "object-add",
+ "arguments": {
+ "qom-type": "throttle-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
+echo "== don't specify 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": "throttle0",
+ "file": "disk0"
+ }
+}
+{ "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..0aed1a2220
--- /dev/null
+++ b/tests/qemu-iotests/184.out
@@ -0,0 +1,302 @@
+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:{\"throttle-group\": \"group0\", \"driver\": \"throttle\", \"file\": {\"driver\": \"qcow2\", \"file\": {\"driver\": \"file\", \"filename\": \"TEST_DIR/t.qcow2\"}}}",
+ "cluster-size": 65536,
+ "format": "throttle",
+ "actual-size": 200704,
+ "dirty-flag": false
+ },
+ "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:{\"throttle-group\": \"group0\", \"driver\": \"throttle\", \"file\": {\"driver\": \"qcow2\", \"file\": {\"driver\": \"file\", \"filename\": \"TEST_DIR/t.qcow2\"}}}",
+ "encryption_key_missing": false
+ },
+ {
+ "iops_rd": 0,
+ "detect_zeroes": "off",
+ "image": {
+ "virtual-size": 67108864,
+ "filename": "TEST_DIR/t.qcow2",
+ "cluster-size": 65536,
+ "format": "qcow2",
+ "actual-size": 200704,
+ "format-specific": {
+ "type": "qcow2",
+ "data": {
+ "compat": "1.1",
+ "lazy-refcounts": false,
+ "refcount-bits": 16,
+ "corrupt": false
+ }
+ },
+ "dirty-flag": false
+ },
+ "iops_wr": 0,
+ "ro": false,
+ "node-name": "disk0",
+ "backing_file_depth": 0,
+ "drv": "qcow2",
+ "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.qcow2",
+ "encryption_key_missing": false
+ },
+ {
+ "iops_rd": 0,
+ "detect_zeroes": "off",
+ "image": {
+ "virtual-size": 197120,
+ "filename": "TEST_DIR/t.qcow2",
+ "format": "file",
+ "actual-size": 200704,
+ "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.qcow2",
+ "encryption_key_missing": false
+ }
+ ]
+}
+{
+ "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": {
+ }
+}
+{
+ "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
+ }
+}
+
+
+== don't specify group ==
+Testing:
+{
+ QMP_VERSION
+}
+{
+ "return": {
+ }
+}
+{
+ "return": {
+ }
+}
+{
+ "error": {
+ "class": "GenericError",
+ "desc": "Parameter 'throttle-group' is missing"
+ }
+}
+{
+ "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 4bd5017008..94e764865a 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
186 rw auto
187 rw auto
diff --git a/tests/test-throttle.c b/tests/test-throttle.c
index bf7a5a648a..948a42c991 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);
@@ -494,45 +497,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 */
@@ -561,7 +564,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);
@@ -588,7 +591,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;
}
@@ -669,6 +672,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);
@@ -679,21 +683,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;
+
+ g_assert(tgm1->throttle_state == NULL);
+ g_assert(tgm2->throttle_state == NULL);
+ g_assert(tgm3->throttle_state == NULL);
- throttle_group_register_blk(blk1, "bar");
- throttle_group_register_blk(blk2, "foo");
- throttle_group_register_blk(blk3, "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(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);
- 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(!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);
@@ -701,29 +709,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)
@@ -731,6 +739,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 b8c524336c..06bf916adc 100644
--- a/util/throttle.c
+++ b/util/throttle.c
@@ -484,3 +484,154 @@ 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) {
+ if (arg->bps_total_max_length > UINT_MAX) {
+ error_setg(errp, "bps-total-max-length value must be in"
+ " the range [0, %u]", UINT_MAX);
+ return;
+ }
+ cfg->buckets[THROTTLE_BPS_TOTAL].burst_length = arg->bps_total_max_length;
+ }
+ if (arg->has_bps_read_max_length) {
+ if (arg->bps_read_max_length > UINT_MAX) {
+ error_setg(errp, "bps-read-max-length value must be in"
+ " the range [0, %u]", UINT_MAX);
+ return;
+ }
+ cfg->buckets[THROTTLE_BPS_READ].burst_length = arg->bps_read_max_length;
+ }
+ if (arg->has_bps_write_max_length) {
+ if (arg->bps_write_max_length > UINT_MAX) {
+ error_setg(errp, "bps-write-max-length value must be in"
+ " the range [0, %u]", UINT_MAX);
+ return;
+ }
+ cfg->buckets[THROTTLE_BPS_WRITE].burst_length = arg->bps_write_max_length;
+ }
+ if (arg->has_iops_total_max_length) {
+ if (arg->iops_total_max_length > UINT_MAX) {
+ error_setg(errp, "iops-total-max-length value must be in"
+ " the range [0, %u]", UINT_MAX);
+ return;
+ }
+ cfg->buckets[THROTTLE_OPS_TOTAL].burst_length = arg->iops_total_max_length;
+ }
+ if (arg->has_iops_read_max_length) {
+ if (arg->iops_read_max_length > UINT_MAX) {
+ error_setg(errp, "iops-read-max-length value must be in"
+ " the range [0, %u]", UINT_MAX);
+ return;
+ }
+ cfg->buckets[THROTTLE_OPS_READ].burst_length = arg->iops_read_max_length;
+ }
+ if (arg->has_iops_write_max_length) {
+ if (arg->iops_write_max_length > UINT_MAX) {
+ error_setg(errp, "iops-write-max-length value must be in"
+ " the range [0, %u]", UINT_MAX);
+ return;
+ }
+ 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_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;
+}