Update the rest of the filter drivers to support BDRV_REQ_WRITE_UNCHANGED. They already forward write request flags to their children, so we just have to announce support for it. This patch does not cover the replication driver because that currently does not support flags at all, and because it just grabs the WRITE permission for its children when it can, so we should be fine just submitting the incoming WRITE_UNCHANGED requests as normal writes. It also does not cover format drivers for similar reasons. They all use bdrv_format_default_perms() as their .bdrv_child_perm() implementation so they just always grab the WRITE permission for their file children whenever possible. In addition, it often would be difficult to ascertain whether incoming unchanging writes end up as unchanging writes in their files. So we just leave them as normal potentially changing writes. Signed-off-by: Max Reitz <mreitz@redhat.com> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> Reviewed-by: Alberto Garcia <berto@igalia.com> Message-id: 20180421132929.21610-7-mreitz@redhat.com Reviewed-by: Kevin Wolf <kwolf@redhat.com> Signed-off-by: Max Reitz <mreitz@redhat.com>
258 lines
8.1 KiB
C
258 lines
8.1 KiB
C
/*
|
|
* 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/option.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 |
|
|
BDRV_REQ_WRITE_UNCHANGED;
|
|
bs->supported_zero_flags = bs->file->bs->supported_zero_flags |
|
|
BDRV_REQ_WRITE_UNCHANGED;
|
|
|
|
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 void coroutine_fn throttle_co_drain_begin(BlockDriverState *bs)
|
|
{
|
|
ThrottleGroupMember *tgm = bs->opaque;
|
|
if (atomic_fetch_inc(&tgm->io_limits_disabled) == 0) {
|
|
throttle_group_restart_tgm(tgm);
|
|
}
|
|
}
|
|
|
|
static void coroutine_fn throttle_co_drain_end(BlockDriverState *bs)
|
|
{
|
|
ThrottleGroupMember *tgm = bs->opaque;
|
|
assert(tgm->io_limits_disabled);
|
|
atomic_dec(&tgm->io_limits_disabled);
|
|
}
|
|
|
|
static BlockDriver bdrv_throttle = {
|
|
.format_name = "throttle",
|
|
.instance_size = sizeof(ThrottleGroupMember),
|
|
|
|
.bdrv_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_block_status = bdrv_co_block_status_from_file,
|
|
|
|
.bdrv_co_drain_begin = throttle_co_drain_begin,
|
|
.bdrv_co_drain_end = throttle_co_drain_end,
|
|
|
|
.is_filter = true,
|
|
};
|
|
|
|
static void bdrv_throttle_init(void)
|
|
{
|
|
bdrv_register(&bdrv_throttle);
|
|
}
|
|
|
|
block_init(bdrv_throttle_init);
|