diff options
author | Manos Pitsidianakis <el13635@mail.ntua.gr> | 2017-08-25 16:20:26 +0300 |
---|---|---|
committer | Kevin Wolf <kwolf@redhat.com> | 2017-09-05 18:12:21 +0200 |
commit | 432d889e55e2614bd0e8bb559af18a61ac217565 (patch) | |
tree | e5ea15da8af7dd17a61903496924dc4c0807fa44 /block/throttle-groups.c | |
parent | f738cfc843055238ad969782db69156929873832 (diff) |
block: convert ThrottleGroup to object with QOM
ThrottleGroup is converted to an object. This will allow the future
throttle block filter drive easy creation and configuration of throttle
groups in QMP and cli.
A new QAPI struct, ThrottleLimits, is introduced to provide a shared
struct for all throttle configuration needs in QMP.
ThrottleGroups can be created via CLI as
-object throttle-group,id=foo,x-iops-total=100,x-..
where x-* are individual limit properties. Since we can't add non-scalar
properties in -object this interface must be used instead. However,
setting these properties must be disabled after initialization because
certain combinations of limits are forbidden and thus configuration
changes should be done in one transaction. The individual properties
will go away when support for non-scalar values in CLI is implemented
and thus are marked as experimental.
ThrottleGroup also has a `limits` property that uses the ThrottleLimits
struct. It can be used to create ThrottleGroups or set the
configuration in existing groups as follows:
{ "execute": "object-add",
"arguments": {
"qom-type": "throttle-group",
"id": "foo",
"props" : {
"limits": {
"iops-total": 100
}
}
}
}
{ "execute" : "qom-set",
"arguments" : {
"path" : "foo",
"property" : "limits",
"value" : {
"iops-total" : 99
}
}
}
This also means a group's configuration can be fetched with qom-get.
Signed-off-by: Manos Pitsidianakis <el13635@mail.ntua.gr>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Reviewed-by: Alberto Garcia <berto@igalia.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Diffstat (limited to 'block/throttle-groups.c')
-rw-r--r-- | block/throttle-groups.c | 424 |
1 files changed, 382 insertions, 42 deletions
diff --git a/block/throttle-groups.c b/block/throttle-groups.c index 7749cf043f..ed1817ec84 100644 --- a/block/throttle-groups.c +++ b/block/throttle-groups.c @@ -25,9 +25,17 @@ #include "qemu/osdep.h" #include "sysemu/block-backend.h" #include "block/throttle-groups.h" +#include "qemu/throttle-options.h" #include "qemu/queue.h" #include "qemu/thread.h" #include "sysemu/qtest.h" +#include "qapi/error.h" +#include "qapi-visit.h" +#include "qom/object.h" +#include "qom/object_interfaces.h" + +static void throttle_group_obj_init(Object *obj); +static void throttle_group_obj_complete(UserCreatable *obj, Error **errp); /* The ThrottleGroup structure (with its ThrottleState) is shared * among different ThrottleGroupMembers and it's independent from @@ -54,6 +62,10 @@ * that ThrottleGroupMember has throttled requests in the queue. */ typedef struct ThrottleGroup { + Object parent_obj; + + /* refuse individual property change if initialization is complete */ + bool is_initialized; char *name; /* This is constant during the lifetime of the group */ QemuMutex lock; /* This lock protects the following four fields */ @@ -63,59 +75,60 @@ typedef struct ThrottleGroup { 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; +} + /* 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,20 +137,15 @@ 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 ThrottleGroupMember's group. The name (and the pointer) @@ -477,6 +485,9 @@ static void write_timer_cb(void *opaque) * 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. * + * 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 @@ -572,9 +583,338 @@ void throttle_group_detach_aio_context(ThrottleGroupMember *tgm) 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_by_name(tg->name) != NULL) { + 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); |