diff options
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); |