From 7dc847ebba953db90853d15f140c20eef74d4fb2 Mon Sep 17 00:00:00 2001 From: Max Reitz Date: Sat, 24 Feb 2018 16:40:29 +0100 Subject: qapi: Replace qobject_to_X(o) by qobject_to(X, o) This patch was generated using the following Coccinelle script: @@ expression Obj; @@ ( - qobject_to_qnum(Obj) + qobject_to(QNum, Obj) | - qobject_to_qstring(Obj) + qobject_to(QString, Obj) | - qobject_to_qdict(Obj) + qobject_to(QDict, Obj) | - qobject_to_qlist(Obj) + qobject_to(QList, Obj) | - qobject_to_qbool(Obj) + qobject_to(QBool, Obj) ) and a bit of manual fix-up for overly long lines and three places in tests/check-qjson.c that Coccinelle did not find. Signed-off-by: Max Reitz Reviewed-by: Alberto Garcia Message-Id: <20180224154033.29559-4-mreitz@redhat.com> Reviewed-by: Eric Blake [eblake: swap order from qobject_to(o, X), rebase to master, also a fix to latent false-positive compiler complaint about hw/i386/acpi-build.c] Signed-off-by: Eric Blake --- qapi/qmp-dispatch.c | 2 +- qapi/qobject-input-visitor.c | 20 ++++++++++---------- qapi/qobject-output-visitor.c | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) (limited to 'qapi') diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c index e31ac4be1f..af537f3d7d 100644 --- a/qapi/qmp-dispatch.c +++ b/qapi/qmp-dispatch.c @@ -26,7 +26,7 @@ static QDict *qmp_dispatch_check_obj(const QObject *request, Error **errp) bool has_exec_key = false; QDict *dict = NULL; - dict = qobject_to_qdict(request); + dict = qobject_to(QDict, request); if (!dict) { error_setg(errp, "QMP input must be a JSON object"); return NULL; diff --git a/qapi/qobject-input-visitor.c b/qapi/qobject-input-visitor.c index 023317b05f..0b5293385e 100644 --- a/qapi/qobject-input-visitor.c +++ b/qapi/qobject-input-visitor.c @@ -137,7 +137,7 @@ static QObject *qobject_input_try_get_object(QObjectInputVisitor *qiv, if (qobject_type(qobj) == QTYPE_QDICT) { assert(name); - ret = qdict_get(qobject_to_qdict(qobj), name); + ret = qdict_get(qobject_to(QDict, qobj), name); if (tos->h && consume && ret) { bool removed = g_hash_table_remove(tos->h, name); assert(removed); @@ -185,7 +185,7 @@ static const char *qobject_input_get_keyval(QObjectInputVisitor *qiv, return NULL; } - qstr = qobject_to_qstring(qobj); + qstr = qobject_to(QString, qobj); if (!qstr) { switch (qobject_type(qobj)) { case QTYPE_QDICT: @@ -224,11 +224,11 @@ static const QListEntry *qobject_input_push(QObjectInputVisitor *qiv, if (qobject_type(obj) == QTYPE_QDICT) { h = g_hash_table_new(g_str_hash, g_str_equal); - qdict_iter(qobject_to_qdict(obj), qdict_add_key, h); + qdict_iter(qobject_to(QDict, obj), qdict_add_key, h); tos->h = h; } else { assert(qobject_type(obj) == QTYPE_QLIST); - tos->entry = qlist_first(qobject_to_qlist(obj)); + tos->entry = qlist_first(qobject_to(QList, obj)); tos->index = -1; } @@ -395,7 +395,7 @@ static void qobject_input_type_int64(Visitor *v, const char *name, int64_t *obj, if (!qobj) { return; } - qnum = qobject_to_qnum(qobj); + qnum = qobject_to(QNum, qobj); if (!qnum || !qnum_get_try_int(qnum, obj)) { error_setg(errp, QERR_INVALID_PARAMETER_TYPE, full_name(qiv, name), "integer"); @@ -430,7 +430,7 @@ static void qobject_input_type_uint64(Visitor *v, const char *name, if (!qobj) { return; } - qnum = qobject_to_qnum(qobj); + qnum = qobject_to(QNum, qobj); if (!qnum) { goto err; } @@ -477,7 +477,7 @@ static void qobject_input_type_bool(Visitor *v, const char *name, bool *obj, if (!qobj) { return; } - qbool = qobject_to_qbool(qobj); + qbool = qobject_to(QBool, qobj); if (!qbool) { error_setg(errp, QERR_INVALID_PARAMETER_TYPE, full_name(qiv, name), "boolean"); @@ -518,7 +518,7 @@ static void qobject_input_type_str(Visitor *v, const char *name, char **obj, if (!qobj) { return; } - qstr = qobject_to_qstring(qobj); + qstr = qobject_to(QString, qobj); if (!qstr) { error_setg(errp, QERR_INVALID_PARAMETER_TYPE, full_name(qiv, name), "string"); @@ -547,7 +547,7 @@ static void qobject_input_type_number(Visitor *v, const char *name, double *obj, if (!qobj) { return; } - qnum = qobject_to_qnum(qobj); + qnum = qobject_to(QNum, qobj); if (!qnum) { error_setg(errp, QERR_INVALID_PARAMETER_TYPE, full_name(qiv, name), "number"); @@ -734,7 +734,7 @@ Visitor *qobject_input_visitor_new_str(const char *str, } return NULL; } - args = qobject_to_qdict(obj); + args = qobject_to(QDict, obj); assert(args); v = qobject_input_visitor_new(QOBJECT(args)); } else { diff --git a/qapi/qobject-output-visitor.c b/qapi/qobject-output-visitor.c index 7c3b42cfe2..877e37eeb8 100644 --- a/qapi/qobject-output-visitor.c +++ b/qapi/qobject-output-visitor.c @@ -92,11 +92,11 @@ static void qobject_output_add_obj(QObjectOutputVisitor *qov, const char *name, switch (qobject_type(cur)) { case QTYPE_QDICT: assert(name); - qdict_put_obj(qobject_to_qdict(cur), name, value); + qdict_put_obj(qobject_to(QDict, cur), name, value); break; case QTYPE_QLIST: assert(!name); - qlist_append_obj(qobject_to_qlist(cur), value); + qlist_append_obj(qobject_to(QList, cur), value); break; default: g_assert_not_reached(); -- cgit v1.2.3-55-g7522 From 532fb532847365f61a9c6e1291b6588a43bc1cc4 Mon Sep 17 00:00:00 2001 From: Max Reitz Date: Sat, 10 Mar 2018 16:14:36 -0600 Subject: qapi: Make more of qobject_to() This patch reworks some places which use either qobject_type() checks plus qobject_to(), where the latter alone is sufficient, or NULL checks plus qobject_type() checks where we can simply do a qobject_to() != NULL check. Signed-off-by: Max Reitz Reviewed-by: Alberto Garcia Message-Id: <20180224154033.29559-6-mreitz@redhat.com> Reviewed-by: Eric Blake [eblake: rebase to qobject_to() parameter ordering] Signed-off-by: Eric Blake --- qapi/qobject-input-visitor.c | 4 ++-- qobject/json-parser.c | 13 +++++++------ qobject/qdict.c | 20 +++++++++++--------- 3 files changed, 20 insertions(+), 17 deletions(-) (limited to 'qapi') diff --git a/qapi/qobject-input-visitor.c b/qapi/qobject-input-visitor.c index 0b5293385e..a7569d5dce 100644 --- a/qapi/qobject-input-visitor.c +++ b/qapi/qobject-input-visitor.c @@ -339,7 +339,7 @@ static GenericList *qobject_input_next_list(Visitor *v, GenericList *tail, QObjectInputVisitor *qiv = to_qiv(v); StackObject *tos = QSLIST_FIRST(&qiv->stack); - assert(tos && tos->obj && qobject_type(tos->obj) == QTYPE_QLIST); + assert(tos && qobject_to(QList, tos->obj)); if (!tos->entry) { return NULL; @@ -353,7 +353,7 @@ static void qobject_input_check_list(Visitor *v, Error **errp) QObjectInputVisitor *qiv = to_qiv(v); StackObject *tos = QSLIST_FIRST(&qiv->stack); - assert(tos && tos->obj && qobject_type(tos->obj) == QTYPE_QLIST); + assert(tos && qobject_to(QList, tos->obj)); if (tos->entry) { error_setg(errp, "Only %u list elements expected in %s", diff --git a/qobject/json-parser.c b/qobject/json-parser.c index 055c7f0272..769b960c9f 100644 --- a/qobject/json-parser.c +++ b/qobject/json-parser.c @@ -276,7 +276,8 @@ static void parser_context_free(JSONParserContext *ctxt) */ static int parse_pair(JSONParserContext *ctxt, QDict *dict, va_list *ap) { - QObject *key = NULL, *value; + QObject *value; + QString *key = NULL; JSONToken *peek, *token; peek = parser_context_peek_token(ctxt); @@ -285,8 +286,8 @@ static int parse_pair(JSONParserContext *ctxt, QDict *dict, va_list *ap) goto out; } - key = parse_value(ctxt, ap); - if (!key || qobject_type(key) != QTYPE_QSTRING) { + key = qobject_to(QString, parse_value(ctxt, ap)); + if (!key) { parse_error(ctxt, peek, "key is not a string in object"); goto out; } @@ -308,14 +309,14 @@ static int parse_pair(JSONParserContext *ctxt, QDict *dict, va_list *ap) goto out; } - qdict_put_obj(dict, qstring_get_str(qobject_to(QString, key)), value); + qdict_put_obj(dict, qstring_get_str(key), value); - qobject_decref(key); + QDECREF(key); return 0; out: - qobject_decref(key); + QDECREF(key); return -1; } diff --git a/qobject/qdict.c b/qobject/qdict.c index 45c8b53361..d1997a0d8a 100644 --- a/qobject/qdict.c +++ b/qobject/qdict.c @@ -882,18 +882,20 @@ QObject *qdict_crumple(const QDict *src, Error **errp) child = qdict_get(two_level, prefix); if (suffix) { - if (child) { - if (qobject_type(child) != QTYPE_QDICT) { + QDict *child_dict = qobject_to(QDict, child); + if (!child_dict) { + if (child) { error_setg(errp, "Key %s prefix is already set as a scalar", prefix); goto error; } - } else { - child = QOBJECT(qdict_new()); - qdict_put_obj(two_level, prefix, child); + + child_dict = qdict_new(); + qdict_put_obj(two_level, prefix, QOBJECT(child_dict)); } + qobject_incref(ent->value); - qdict_put_obj(qobject_to(QDict, child), suffix, ent->value); + qdict_put_obj(child_dict, suffix, ent->value); } else { if (child) { error_setg(errp, "Key %s prefix is already set as a dict", @@ -913,9 +915,9 @@ QObject *qdict_crumple(const QDict *src, Error **errp) multi_level = qdict_new(); for (ent = qdict_first(two_level); ent != NULL; ent = qdict_next(two_level, ent)) { - - if (qobject_type(ent->value) == QTYPE_QDICT) { - child = qdict_crumple(qobject_to(QDict, ent->value), errp); + QDict *dict = qobject_to(QDict, ent->value); + if (dict) { + child = qdict_crumple(dict, errp); if (!child) { goto error; } -- cgit v1.2.3-55-g7522 From 4f7be2806e672fa232e406ac5eec26789b9d5f85 Mon Sep 17 00:00:00 2001 From: Max Reitz Date: Sat, 24 Feb 2018 16:40:33 +0100 Subject: block: Deprecate "backing": "" We have a clear replacement, so let's deprecate it. Signed-off-by: Max Reitz Reviewed-by: Eric Blake Reviewed-by: Alberto Garcia Message-Id: <20180224154033.29559-8-mreitz@redhat.com> Signed-off-by: Eric Blake --- block.c | 4 ++++ qapi/block-core.json | 4 ++-- qemu-doc.texi | 7 +++++++ qemu-options.hx | 4 ++-- 4 files changed, 15 insertions(+), 4 deletions(-) (limited to 'qapi') diff --git a/block.c b/block.c index ab77ba0233..a2caadf0a0 100644 --- a/block.c +++ b/block.c @@ -2649,6 +2649,10 @@ static BlockDriverState *bdrv_open_inherit(const char *filename, if (qobject_to(QNull, qdict_get(options, "backing")) != NULL || (backing && *backing == '\0')) { + if (backing) { + warn_report("Use of \"backing\": \"\" is deprecated; " + "use \"backing\": null instead"); + } flags |= BDRV_O_NO_BACKING; qdict_del(options, "backing"); } diff --git a/qapi/block-core.json b/qapi/block-core.json index 5b0ad1a8b7..f374be4c11 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -1174,7 +1174,7 @@ # @overlay: reference to the existing block device that will become # the overlay of @node, as part of creating the snapshot. # It must not have a current backing file (this can be -# achieved by passing "backing": "" to blockdev-add). +# achieved by passing "backing": null to blockdev-add). # # Since: 2.5 ## @@ -1347,7 +1347,7 @@ # "node-name": "node1534", # "file": { "driver": "file", # "filename": "hd1.qcow2" }, -# "backing": "" } } +# "backing": null } } # # <- { "return": {} } # diff --git a/qemu-doc.texi b/qemu-doc.texi index f8da9b8135..89fa80518a 100644 --- a/qemu-doc.texi +++ b/qemu-doc.texi @@ -2789,6 +2789,13 @@ support page sizes < 4096 any longer. The ``xlnx-ep108'' machine has been replaced by the ``xlnx-zcu102'' machine. The ``xlnx-zcu102'' machine has the same features and capabilites in QEMU. +@section Block device options + +@subsection "backing": "" (since 2.12.0) + +In order to prevent QEMU from automatically opening an image's backing +chain, use ``"backing": null'' instead. + @node License @appendix License diff --git a/qemu-options.hx b/qemu-options.hx index 6113bce08a..74158e7493 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -743,8 +743,8 @@ Reference to or definition of the data source block driver node @item backing Reference to or definition of the backing file block device (default is taken -from the image file). It is allowed to pass an empty string here in order to -disable the default backing file. +from the image file). It is allowed to pass @code{null} here in order to disable +the default backing file. @item lazy-refcounts Whether to enable the lazy refcounts feature (on/off; default is taken from the -- cgit v1.2.3-55-g7522 From 02130314d8c71743e6d1fefc2b08a608516bccc7 Mon Sep 17 00:00:00 2001 From: Peter Xu Date: Fri, 9 Mar 2018 16:59:53 +0800 Subject: qmp: introduce QMPCapability There were no QMP capabilities defined. Define the first capability, "oob", to allow out-of-band messages. After this patch, we will allow QMP clients to enable QMP capabilities when sending the first "qmp_capabilities" command. Originally we are starting QMP session with no arguments like: { "execute": "qmp_capabilities" } Now we can enable some QMP capabilities using (take OOB as example, which is the only capability that we support): { "execute": "qmp_capabilities", "arguments": { "enable": [ "oob" ] } } When the "arguments" key is not provided, no capability is enabled. For capability "oob", the monitor needs to be run on a dedicated IO thread, otherwise the command will fail. For example, trying to enable OOB on a MUXed typed QMP monitor will fail. One thing to mention is that QMP capabilities are per-monitor, and also when the connection is closed due to some reason, the capabilities will be reset. Also, touch up qmp-test.c to test the new bits. Signed-off-by: Peter Xu Message-Id: <20180309090006.10018-11-peterx@redhat.com> Reviewed-by: Eric Blake [eblake: touch up commit message] Signed-off-by: Eric Blake --- monitor.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++---- qapi/misc.json | 32 ++++++++++++++++++++--- tests/qmp-test.c | 10 +++++++- 3 files changed, 110 insertions(+), 9 deletions(-) (limited to 'qapi') diff --git a/monitor.c b/monitor.c index 44b2fa2f4a..d31ec703e3 100644 --- a/monitor.c +++ b/monitor.c @@ -59,6 +59,7 @@ #include "qapi/qmp/qjson.h" #include "qapi/qmp/json-streamer.h" #include "qapi/qmp/json-parser.h" +#include "qapi/qmp/qlist.h" #include "qom/object_interfaces.h" #include "trace-root.h" #include "trace/control.h" @@ -170,6 +171,7 @@ typedef struct { * mode. */ QmpCommandList *commands; + bool qmp_caps[QMP_CAPABILITY__MAX]; } MonitorQMP; /* @@ -1042,8 +1044,42 @@ static void monitor_init_qmp_commands(void) qmp_marshal_qmp_capabilities, QCO_NO_OPTIONS); } -void qmp_qmp_capabilities(Error **errp) +static void qmp_caps_check(Monitor *mon, QMPCapabilityList *list, + Error **errp) +{ + for (; list; list = list->next) { + assert(list->value < QMP_CAPABILITY__MAX); + switch (list->value) { + case QMP_CAPABILITY_OOB: + if (!mon->use_io_thr) { + /* + * Out-Of-Band only works with monitors that are + * running on dedicated IOThread. + */ + error_setg(errp, "This monitor does not support " + "Out-Of-Band (OOB)"); + return; + } + break; + default: + break; + } + } +} + +/* This function should only be called after capabilities are checked. */ +static void qmp_caps_apply(Monitor *mon, QMPCapabilityList *list) { + for (; list; list = list->next) { + mon->qmp.qmp_caps[list->value] = true; + } +} + +void qmp_qmp_capabilities(bool has_enable, QMPCapabilityList *enable, + Error **errp) +{ + Error *local_err = NULL; + if (cur_mon->qmp.commands == &qmp_commands) { error_set(errp, ERROR_CLASS_COMMAND_NOT_FOUND, "Capabilities negotiation is already complete, command " @@ -1051,6 +1087,21 @@ void qmp_qmp_capabilities(Error **errp) return; } + /* Enable QMP capabilities provided by the client if applicable. */ + if (has_enable) { + qmp_caps_check(cur_mon, enable, &local_err); + if (local_err) { + /* + * Failed check on any of the capabilities will fail the + * entire command (and thus not apply any of the other + * capabilities that were also requested). + */ + error_propagate(errp, local_err); + return; + } + qmp_caps_apply(cur_mon, enable); + } + cur_mon->qmp.commands = &qmp_commands; } @@ -3896,14 +3947,29 @@ void monitor_resume(Monitor *mon) readline_show_prompt(mon->rs); } -static QObject *get_qmp_greeting(void) +static QObject *get_qmp_greeting(Monitor *mon) { + QList *cap_list = qlist_new(); QObject *ver = NULL; + QMPCapability cap; qmp_marshal_query_version(NULL, &ver, NULL); - return qobject_from_jsonf("{'QMP': {'version': %p, 'capabilities': []}}", - ver); + for (cap = 0; cap < QMP_CAPABILITY__MAX; cap++) { + if (!mon->use_io_thr && cap == QMP_CAPABILITY_OOB) { + /* Monitors that are not using IOThread won't support OOB */ + continue; + } + qlist_append(cap_list, qstring_from_str(QMPCapability_str(cap))); + } + + return qobject_from_jsonf("{'QMP': {'version': %p, 'capabilities': %p}}", + ver, cap_list); +} + +static void monitor_qmp_caps_reset(Monitor *mon) +{ + memset(mon->qmp.qmp_caps, 0, sizeof(mon->qmp.qmp_caps)); } static void monitor_qmp_event(void *opaque, int event) @@ -3914,7 +3980,8 @@ static void monitor_qmp_event(void *opaque, int event) switch (event) { case CHR_EVENT_OPENED: mon->qmp.commands = &qmp_cap_negotiation_commands; - data = get_qmp_greeting(); + monitor_qmp_caps_reset(mon); + data = get_qmp_greeting(mon); monitor_json_emitter(mon, data); qobject_decref(data); mon_refcount++; diff --git a/qapi/misc.json b/qapi/misc.json index 6150b9a003..564216ae97 100644 --- a/qapi/misc.json +++ b/qapi/misc.json @@ -10,21 +10,47 @@ # # Enable QMP capabilities. # -# Arguments: None. +# Arguments: +# +# @enable: An optional list of QMPCapability values to enable. The +# client must not enable any capability that is not +# mentioned in the QMP greeting message. If the field is not +# provided, it means no QMP capabilities will be enabled. +# (since 2.12) # # Example: # -# -> { "execute": "qmp_capabilities" } +# -> { "execute": "qmp_capabilities", +# "arguments": { "enable": [ "oob" ] } } # <- { "return": {} } # # Notes: This command is valid exactly when first connecting: it must be # issued before any other command will be accepted, and will fail once the # monitor is accepting other commands. (see qemu docs/interop/qmp-spec.txt) # +# The QMP client needs to explicitly enable QMP capabilities, otherwise +# all the QMP capabilities will be turned off by default. +# # Since: 0.13 # ## -{ 'command': 'qmp_capabilities' } +{ 'command': 'qmp_capabilities', + 'data': { '*enable': [ 'QMPCapability' ] } } + +## +# @QMPCapability: +# +# Enumeration of capabilities to be advertised during initial client +# connection, used for agreeing on particular QMP extension behaviors. +# +# @oob: QMP ability to support Out-Of-Band requests. +# (Please refer to qmp-spec.txt for more information on OOB) +# +# Since: 2.12 +# +## +{ 'enum': 'QMPCapability', + 'data': [ 'oob' ] } ## # @VersionTriple: diff --git a/tests/qmp-test.c b/tests/qmp-test.c index 7470c6b754..d1fa1cb217 100644 --- a/tests/qmp-test.c +++ b/tests/qmp-test.c @@ -20,6 +20,7 @@ #include "qapi/qobject-input-visitor.h" #include "qapi/util.h" #include "qapi/visitor.h" +#include "qapi/qmp/qstring.h" const char common_args[] = "-nodefaults -machine none"; @@ -79,6 +80,8 @@ static void test_qmp_protocol(void) QDict *resp, *q, *ret; QList *capabilities; QTestState *qts; + const QListEntry *entry; + QString *qstr; qts = qtest_init_without_qmp_handshake(common_args); @@ -88,7 +91,12 @@ static void test_qmp_protocol(void) g_assert(q); test_version(qdict_get(q, "version")); capabilities = qdict_get_qlist(q, "capabilities"); - g_assert(capabilities && qlist_empty(capabilities)); + g_assert(capabilities); + entry = qlist_first(capabilities); + g_assert(entry); + qstr = qobject_to(QString, entry->value); + g_assert(qstr); + g_assert_cmpstr(qstring_get_str(qstr), ==, "oob"); QDECREF(resp); /* Test valid command before handshake */ -- cgit v1.2.3-55-g7522 From 8167d8bd363f9ee22c9ee53566a51cfe886d39f1 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Fri, 16 Mar 2018 07:33:35 -0500 Subject: qmp: add new event "command-dropped" This event will be emitted if one QMP command is dropped. Also, declare an enum for the reasons. Reviewed-by: Fam Zheng Reviewed-by: Stefan Hajnoczi Signed-off-by: Peter Xu Message-Id: <20180309090006.10018-16-peterx@redhat.com> Reviewed-by: Eric Blake [eblake: rebase to master] Signed-off-by: Eric Blake --- qapi/misc.json | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) (limited to 'qapi') diff --git a/qapi/misc.json b/qapi/misc.json index 564216ae97..1545e41620 100644 --- a/qapi/misc.json +++ b/qapi/misc.json @@ -3390,3 +3390,40 @@ # ## { 'command': 'query-sev-capabilities', 'returns': 'SevCapability' } + +## +# @CommandDropReason: +# +# Reasons that caused one command to be dropped. +# +# @queue-full: the command queue is full. This can only occur when +# the client sends a new non-oob command before the +# response to the previous non-oob command has been +# received. +# +# Since: 2.12 +## +{ 'enum': 'CommandDropReason', + 'data': [ 'queue-full' ] } + +## +# @COMMAND_DROPPED: +# +# Emitted when a command is dropped due to some reason. Commands can +# only be dropped when the oob capability is enabled. +# +# @id: The dropped command's "id" field. +# +# @reason: The reason why the command is dropped. +# +# Since: 2.12 +# +# Example: +# +# { "event": "COMMAND_DROPPED", +# "data": {"result": {"id": "libvirt-102", +# "reason": "queue-full" } } } +# +## +{ 'event': 'COMMAND_DROPPED' , + 'data': { 'id': 'any', 'reason': 'CommandDropReason' } } -- cgit v1.2.3-55-g7522 From 876c67512e2af8c05686faa9f9ff49b38d7a392c Mon Sep 17 00:00:00 2001 From: Peter Xu Date: Fri, 9 Mar 2018 17:00:00 +0800 Subject: qapi: introduce new cmd option "allow-oob" Here "oob" stands for "Out-Of-Band". When "allow-oob" is set, it means the command allows out-of-band execution. The "oob" idea is proposed by Markus Armbruster in following thread: https://lists.gnu.org/archive/html/qemu-devel/2017-09/msg02057.html This new "allow-oob" boolean will be exposed by "query-qmp-schema" as well for command entries, so that QMP clients can know which commands can be used in out-of-band calls. For example the command "migrate" originally looks like: {"name": "migrate", "ret-type": "17", "meta-type": "command", "arg-type": "86"} And it'll be changed into: {"name": "migrate", "ret-type": "17", "allow-oob": false, "meta-type": "command", "arg-type": "86"} This patch only provides the QMP interface level changes. It does not contain the real out-of-band execution implementation yet. Suggested-by: Markus Armbruster Reviewed-by: Stefan Hajnoczi Reviewed-by: Fam Zheng Signed-off-by: Peter Xu Message-Id: <20180309090006.10018-18-peterx@redhat.com> Reviewed-by: Eric Blake [eblake: rebase on introspection done by qlit] Signed-off-by: Eric Blake --- include/qapi/qmp/dispatch.h | 5 +++-- qapi/introspect.json | 6 +++++- scripts/qapi/commands.py | 18 +++++++++++++----- scripts/qapi/common.py | 15 ++++++++++----- scripts/qapi/doc.py | 2 +- scripts/qapi/introspect.py | 7 +++++-- tests/qapi-schema/test-qapi.py | 2 +- 7 files changed, 38 insertions(+), 17 deletions(-) (limited to 'qapi') diff --git a/include/qapi/qmp/dispatch.h b/include/qapi/qmp/dispatch.h index 1e694b5ecf..26eb13ff41 100644 --- a/include/qapi/qmp/dispatch.h +++ b/include/qapi/qmp/dispatch.h @@ -20,8 +20,9 @@ typedef void (QmpCommandFunc)(QDict *, QObject **, Error **); typedef enum QmpCommandOptions { - QCO_NO_OPTIONS = 0x0, - QCO_NO_SUCCESS_RESP = 0x1, + QCO_NO_OPTIONS = 0x0, + QCO_NO_SUCCESS_RESP = (1U << 0), + QCO_ALLOW_OOB = (1U << 1), } QmpCommandOptions; typedef struct QmpCommand diff --git a/qapi/introspect.json b/qapi/introspect.json index 5b3e6e9d78..c7f67b7d78 100644 --- a/qapi/introspect.json +++ b/qapi/introspect.json @@ -259,12 +259,16 @@ # # @ret-type: the name of the command's result type. # +# @allow-oob: whether the command allows out-of-band execution. +# (Since: 2.12) +# # TODO: @success-response (currently irrelevant, because it's QGA, not QMP) # # Since: 2.5 ## { 'struct': 'SchemaInfoCommand', - 'data': { 'arg-type': 'str', 'ret-type': 'str' } } + 'data': { 'arg-type': 'str', 'ret-type': 'str', + 'allow-oob': 'bool' } } ## # @SchemaInfoEvent: diff --git a/scripts/qapi/commands.py b/scripts/qapi/commands.py index 21a7e0dbe6..0c5da3a54d 100644 --- a/scripts/qapi/commands.py +++ b/scripts/qapi/commands.py @@ -193,10 +193,18 @@ out: return ret -def gen_register_command(name, success_response): - options = 'QCO_NO_OPTIONS' +def gen_register_command(name, success_response, allow_oob): + options = [] + if not success_response: - options = 'QCO_NO_SUCCESS_RESP' + options += ['QCO_NO_SUCCESS_RESP'] + if allow_oob: + options += ['QCO_ALLOW_OOB'] + + if not options: + options = ['QCO_NO_OPTIONS'] + + options = " | ".join(options) ret = mcgen(''' qmp_register_command(cmds, "%(name)s", @@ -268,7 +276,7 @@ void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds); genc.add(gen_registry(self._regy, self._prefix)) def visit_command(self, name, info, arg_type, ret_type, - gen, success_response, boxed): + gen, success_response, boxed, allow_oob): if not gen: return self._genh.add(gen_command_decl(name, arg_type, boxed, ret_type)) @@ -277,7 +285,7 @@ void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds); self._genc.add(gen_marshal_output(ret_type)) self._genh.add(gen_marshal_decl(name)) self._genc.add(gen_marshal(name, arg_type, boxed, ret_type)) - self._regy += gen_register_command(name, success_response) + self._regy += gen_register_command(name, success_response, allow_oob) def gen_commands(schema, output_dir, prefix): diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py index 97e9060b67..2c05e3c284 100644 --- a/scripts/qapi/common.py +++ b/scripts/qapi/common.py @@ -921,7 +921,8 @@ def check_exprs(exprs): elif 'command' in expr: meta = 'command' check_keys(expr_elem, 'command', [], - ['data', 'returns', 'gen', 'success-response', 'boxed']) + ['data', 'returns', 'gen', 'success-response', + 'boxed', 'allow-oob']) elif 'event' in expr: meta = 'event' check_keys(expr_elem, 'event', [], ['data', 'boxed']) @@ -1044,7 +1045,7 @@ class QAPISchemaVisitor(object): pass def visit_command(self, name, info, arg_type, ret_type, - gen, success_response, boxed): + gen, success_response, boxed, allow_oob): pass def visit_event(self, name, info, arg_type, boxed): @@ -1421,7 +1422,7 @@ class QAPISchemaAlternateType(QAPISchemaType): class QAPISchemaCommand(QAPISchemaEntity): def __init__(self, name, info, doc, arg_type, ret_type, - gen, success_response, boxed): + gen, success_response, boxed, allow_oob): QAPISchemaEntity.__init__(self, name, info, doc) assert not arg_type or isinstance(arg_type, str) assert not ret_type or isinstance(ret_type, str) @@ -1432,6 +1433,7 @@ class QAPISchemaCommand(QAPISchemaEntity): self.gen = gen self.success_response = success_response self.boxed = boxed + self.allow_oob = allow_oob def check(self, schema): if self._arg_type_name: @@ -1455,7 +1457,8 @@ class QAPISchemaCommand(QAPISchemaEntity): def visit(self, visitor): visitor.visit_command(self.name, self.info, self.arg_type, self.ret_type, - self.gen, self.success_response, self.boxed) + self.gen, self.success_response, + self.boxed, self.allow_oob) class QAPISchemaEvent(QAPISchemaEntity): @@ -1674,6 +1677,7 @@ class QAPISchema(object): gen = expr.get('gen', True) success_response = expr.get('success-response', True) boxed = expr.get('boxed', False) + allow_oob = expr.get('allow-oob', False) if isinstance(data, OrderedDict): data = self._make_implicit_object_type( name, info, doc, 'arg', self._make_members(data, info)) @@ -1681,7 +1685,8 @@ class QAPISchema(object): assert len(rets) == 1 rets = self._make_array_type(rets[0], info) self._def_entity(QAPISchemaCommand(name, info, doc, data, rets, - gen, success_response, boxed)) + gen, success_response, + boxed, allow_oob)) def _def_event(self, expr, info, doc): name = expr['event'] diff --git a/scripts/qapi/doc.py b/scripts/qapi/doc.py index 79d11bbe9b..9b312b2c51 100644 --- a/scripts/qapi/doc.py +++ b/scripts/qapi/doc.py @@ -227,7 +227,7 @@ class QAPISchemaGenDocVisitor(qapi.common.QAPISchemaVisitor): body=texi_entity(doc, 'Members'))) def visit_command(self, name, info, arg_type, ret_type, - gen, success_response, boxed): + gen, success_response, boxed, allow_oob): doc = self.cur_doc if boxed: body = texi_body(doc) diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py index 1d46a6d6b6..f9e67e8227 100644 --- a/scripts/qapi/introspect.py +++ b/scripts/qapi/introspect.py @@ -41,6 +41,8 @@ def to_qlit(obj, level=0, suppress_first_indent=False): ret += 'QLIT_QDICT(((QLitDictEntry[]) {\n' ret += ',\n'.join(elts) + '\n' ret += indent(level) + '}))' + elif isinstance(obj, bool): + ret += 'QLIT_QBOOL(%s)' % ('true' if obj else 'false') else: assert False # not implemented return ret @@ -170,12 +172,13 @@ const QLitObject %(c_name)s = %(c_string)s; for m in variants.variants]}) def visit_command(self, name, info, arg_type, ret_type, - gen, success_response, boxed): + gen, success_response, boxed, allow_oob): arg_type = arg_type or self._schema.the_empty_object_type ret_type = ret_type or self._schema.the_empty_object_type self._gen_qlit(name, 'command', {'arg-type': self._use_type(arg_type), - 'ret-type': self._use_type(ret_type)}) + 'ret-type': self._use_type(ret_type), + 'allow-oob': allow_oob}) def visit_event(self, name, info, arg_type, boxed): arg_type = arg_type or self._schema.the_empty_object_type diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py index 67e417e298..10e68b01d9 100644 --- a/tests/qapi-schema/test-qapi.py +++ b/tests/qapi-schema/test-qapi.py @@ -42,7 +42,7 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor): self._print_variants(variants) def visit_command(self, name, info, arg_type, ret_type, - gen, success_response, boxed): + gen, success_response, boxed, allow_oob): print('command %s %s -> %s' % \ (name, arg_type and arg_type.name, ret_type and ret_type.name)) print(' gen=%s success_response=%s boxed=%s' % \ -- cgit v1.2.3-55-g7522 From cf869d53172920536a14180a83292b240e9d0545 Mon Sep 17 00:00:00 2001 From: Peter Xu Date: Sat, 10 Mar 2018 20:38:05 -0600 Subject: qmp: support out-of-band (oob) execution Having "allow-oob":true for a command does not mean that this command will always be run in out-of-band mode. The out-of-band quick path will only be executed if we specify the extra "run-oob" flag when sending the QMP request: { "execute": "command-that-allows-oob", "arguments": { ... }, "control": { "run-oob": true } } The "control" key is introduced to store this extra flag. "control" field is used to store arguments that are shared by all the commands, rather than command specific arguments. Let "run-oob" be the first. Note that in the patch I exported qmp_dispatch_check_obj() to be used to check the request earlier, and at the same time allowed "id" field to be there since actually we always allow that. Reviewed-by: Stefan Hajnoczi Signed-off-by: Peter Xu Message-Id: <20180309090006.10018-19-peterx@redhat.com> Reviewed-by: Eric Blake [eblake: rebase to qobject_to(), spelling fix] Signed-off-by: Eric Blake --- include/qapi/qmp/dispatch.h | 2 ++ monitor.c | 84 ++++++++++++++++++++++++++++++++++++++++----- qapi/qmp-dispatch.c | 33 +++++++++++++++++- trace-events | 2 ++ 4 files changed, 111 insertions(+), 10 deletions(-) (limited to 'qapi') diff --git a/include/qapi/qmp/dispatch.h b/include/qapi/qmp/dispatch.h index 26eb13ff41..ffb4652f71 100644 --- a/include/qapi/qmp/dispatch.h +++ b/include/qapi/qmp/dispatch.h @@ -48,6 +48,8 @@ bool qmp_command_is_enabled(const QmpCommand *cmd); const char *qmp_command_name(const QmpCommand *cmd); bool qmp_has_success_response(const QmpCommand *cmd); QObject *qmp_build_error_object(Error *err); +QDict *qmp_dispatch_check_obj(const QObject *request, Error **errp); +bool qmp_is_oob(QDict *dict); typedef void (*qmp_cmd_callback_fn)(QmpCommand *cmd, void *opaque); diff --git a/monitor.c b/monitor.c index 017ddca6e2..405476fd09 100644 --- a/monitor.c +++ b/monitor.c @@ -1113,6 +1113,44 @@ static void qmp_caps_apply(Monitor *mon, QMPCapabilityList *list) } } +/* + * Return true if check successful, or false otherwise. When false is + * returned, detailed error will be in errp if provided. + */ +static bool qmp_cmd_oob_check(Monitor *mon, QDict *req, Error **errp) +{ + const char *command; + QmpCommand *cmd; + + command = qdict_get_try_str(req, "execute"); + if (!command) { + error_setg(errp, "Command field 'execute' missing"); + return false; + } + + cmd = qmp_find_command(mon->qmp.commands, command); + if (!cmd) { + error_set(errp, ERROR_CLASS_COMMAND_NOT_FOUND, + "The command %s has not been found", command); + return false; + } + + if (qmp_is_oob(req)) { + if (!qmp_oob_enabled(mon)) { + error_setg(errp, "Please enable Out-Of-Band first " + "for the session during capabilities negotiation"); + return false; + } + if (!(cmd->options & QCO_ALLOW_OOB)) { + error_setg(errp, "The command %s does not support OOB", + command); + return false; + } + } + + return true; +} + void qmp_qmp_capabilities(bool has_enable, QMPCapabilityList *enable, Error **errp) { @@ -4004,6 +4042,7 @@ static void monitor_qmp_bh_dispatcher(void *data) QMPRequest *req_obj = monitor_qmp_requests_pop_one(); if (req_obj) { + trace_monitor_qmp_cmd_in_band(qobject_get_try_str(req_obj->id) ?: ""); monitor_qmp_dispatch_one(req_obj); /* Reschedule instead of looping so the main loop stays responsive */ qemu_bh_schedule(mon_global.qmp_dispatcher_bh); @@ -4027,17 +4066,31 @@ static void handle_qmp_command(JSONMessageParser *parser, GQueue *tokens) error_setg(&err, QERR_JSON_PARSING); } if (err) { - monitor_qmp_respond(mon, NULL, err, NULL); - qobject_decref(req); - return; + goto err; + } + + /* Check against the request in general layout */ + qdict = qmp_dispatch_check_obj(req, &err); + if (!qdict) { + goto err; } - qdict = qobject_to(QDict, req); - if (qdict) { - id = qdict_get(qdict, "id"); - qobject_incref(id); - qdict_del(qdict, "id"); - } /* else will fail qmp_dispatch() */ + /* Check against OOB specific */ + if (!qmp_cmd_oob_check(mon, qdict, &err)) { + goto err; + } + + id = qdict_get(qdict, "id"); + + /* When OOB is enabled, the "id" field is mandatory. */ + if (qmp_oob_enabled(mon) && !id) { + error_setg(&err, "Out-Of-Band capability requires that " + "every command contains an 'id' field"); + goto err; + } + + qobject_incref(id); + qdict_del(qdict, "id"); req_obj = g_new0(QMPRequest, 1); req_obj->mon = mon; @@ -4045,6 +4098,14 @@ static void handle_qmp_command(JSONMessageParser *parser, GQueue *tokens) req_obj->req = req; req_obj->need_resume = false; + if (qmp_is_oob(qdict)) { + /* Out-Of-Band (OOB) requests are executed directly in parser. */ + trace_monitor_qmp_cmd_out_of_band(qobject_get_try_str(req_obj->id) + ?: ""); + monitor_qmp_dispatch_one(req_obj); + return; + } + /* Protect qmp_requests and fetching its length. */ qemu_mutex_lock(&mon->qmp.qmp_queue_lock); @@ -4081,6 +4142,11 @@ static void handle_qmp_command(JSONMessageParser *parser, GQueue *tokens) /* Kick the dispatcher routine */ qemu_bh_schedule(mon_global.qmp_dispatcher_bh); + return; + +err: + monitor_qmp_respond(mon, NULL, err, NULL); + qobject_decref(req); } static void monitor_qmp_read(void *opaque, const uint8_t *buf, int size) diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c index af537f3d7d..dd05907265 100644 --- a/qapi/qmp-dispatch.c +++ b/qapi/qmp-dispatch.c @@ -17,8 +17,9 @@ #include "qapi/qmp/json-parser.h" #include "qapi/qmp/qdict.h" #include "qapi/qmp/qjson.h" +#include "qapi/qmp/qbool.h" -static QDict *qmp_dispatch_check_obj(const QObject *request, Error **errp) +QDict *qmp_dispatch_check_obj(const QObject *request, Error **errp) { const QDictEntry *ent; const char *arg_name; @@ -50,6 +51,14 @@ static QDict *qmp_dispatch_check_obj(const QObject *request, Error **errp) "QMP input member 'arguments' must be an object"); return NULL; } + } else if (!strcmp(arg_name, "id")) { + continue; + } else if (!strcmp(arg_name, "control")) { + if (qobject_type(arg_obj) != QTYPE_QDICT) { + error_setg(errp, + "QMP input member 'control' must be a dict"); + return NULL; + } } else { error_setg(errp, "QMP input member '%s' is unexpected", arg_name); @@ -120,6 +129,28 @@ QObject *qmp_build_error_object(Error *err) error_get_pretty(err)); } +/* + * Detect whether a request should be run out-of-band, by quickly + * peeking at whether we have: { "control": { "run-oob": true } }. By + * default commands are run in-band. + */ +bool qmp_is_oob(QDict *dict) +{ + QBool *bool_obj; + + dict = qdict_get_qdict(dict, "control"); + if (!dict) { + return false; + } + + bool_obj = qobject_to(QBool, qdict_get(dict, "run-oob")); + if (!bool_obj) { + return false; + } + + return qbool_get_bool(bool_obj); +} + QObject *qmp_dispatch(QmpCommandList *cmds, QObject *request) { Error *err = NULL; diff --git a/trace-events b/trace-events index 56670fecc8..22c7dd4ac6 100644 --- a/trace-events +++ b/trace-events @@ -48,6 +48,8 @@ monitor_protocol_event_queue(uint32_t event, void *qdict, uint64_t rate) "event= handle_hmp_command(void *mon, const char *cmdline) "mon %p cmdline: %s" handle_qmp_command(void *mon, const char *req) "mon %p req: %s" monitor_suspend(void *ptr, int cnt) "mon %p: %d" +monitor_qmp_cmd_in_band(const char *id) "%s" +monitor_qmp_cmd_out_of_band(const char *id) "%s" # dma-helpers.c dma_blk_io(void *dbs, void *bs, int64_t offset, bool to_dev) "dbs=%p bs=%p offset=%" PRId64 " to_dev=%d" -- cgit v1.2.3-55-g7522 From 469638f9cb3fde767fbc207f62fc3735eea96ef1 Mon Sep 17 00:00:00 2001 From: Peter Xu Date: Fri, 9 Mar 2018 17:00:04 +0800 Subject: qmp: add command "x-oob-test" This command is only used to test OOB functionality. It should not be used for any other purposes. Reviewed-by: Stefan Hajnoczi Reviewed-by: Fam Zheng Signed-off-by: Peter Xu Message-Id: <20180309090006.10018-22-peterx@redhat.com> Reviewed-by: Eric Blake [eblake: grammar tweak] Signed-off-by: Eric Blake --- qapi/misc.json | 18 ++++++++++++++++++ qmp.c | 16 ++++++++++++++++ 2 files changed, 34 insertions(+) (limited to 'qapi') diff --git a/qapi/misc.json b/qapi/misc.json index 1545e41620..c31fc983f3 100644 --- a/qapi/misc.json +++ b/qapi/misc.json @@ -3427,3 +3427,21 @@ ## { 'event': 'COMMAND_DROPPED' , 'data': { 'id': 'any', 'reason': 'CommandDropReason' } } + +## +# @x-oob-test: +# +# Test OOB functionality. When sending this command with lock=true, +# it'll try to hang the dispatcher. When sending it with lock=false, +# it'll try to notify the locked thread to continue. Note: it should +# only be used by QMP test program rather than anything else. +# +# Since: 2.12 +# +# Example: +# +# { "execute": "x-oob-test", +# "arguments": { "lock": true } } +## +{ 'command': 'x-oob-test', 'data' : { 'lock': 'bool' }, + 'allow-oob': true } diff --git a/qmp.c b/qmp.c index 7e606d8486..ea3760acb1 100644 --- a/qmp.c +++ b/qmp.c @@ -770,3 +770,19 @@ MemoryInfo *qmp_query_memory_size_summary(Error **errp) return mem_info; } + +static QemuSemaphore x_oob_test_sem; + +static void __attribute__((constructor)) x_oob_test_init(void) +{ + qemu_sem_init(&x_oob_test_sem, 0); +} + +void qmp_x_oob_test(bool lock, Error **errp) +{ + if (lock) { + qemu_sem_wait(&x_oob_test_sem); + } else { + qemu_sem_post(&x_oob_test_sem); + } +} -- cgit v1.2.3-55-g7522 From 7e5c776d15b1fc0cda00c255ba035bdf81dbaa31 Mon Sep 17 00:00:00 2001 From: Vladimir Sementsov-Ogievskiy Date: Fri, 9 Mar 2018 19:52:12 +0300 Subject: qapi: add block latency histogram interface Set (and clear) histograms through new command block-latency-histogram-set and show new statistics in query-blockstats results. For now, the command is marked experimental with prefix 'x-', to gain experience with the interface without being stuck with design decisions. Signed-off-by: Vladimir Sementsov-Ogievskiy Message-Id: <20180309165212.97144-3-vsementsov@virtuozzo.com> Reviewed-by: Eric Blake Reviewed-by: Stefan Hajnoczi [eblake: fix typos, mention x- prefix in commit message] Signed-off-by: Eric Blake --- block/qapi.c | 41 +++++++++++++++++++ blockdev.c | 43 ++++++++++++++++++++ qapi/block-core.json | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 194 insertions(+), 1 deletion(-) (limited to 'qapi') diff --git a/block/qapi.c b/block/qapi.c index f2e0aa2cbe..04c6fc69b9 100644 --- a/block/qapi.c +++ b/block/qapi.c @@ -394,6 +394,37 @@ static void bdrv_query_info(BlockBackend *blk, BlockInfo **p_info, qapi_free_BlockInfo(info); } +static uint64List *uint64_list(uint64_t *list, int size) +{ + int i; + uint64List *out_list = NULL; + uint64List **pout_list = &out_list; + + for (i = 0; i < size; i++) { + uint64List *entry = g_new(uint64List, 1); + entry->value = list[i]; + *pout_list = entry; + pout_list = &entry->next; + } + + *pout_list = NULL; + + return out_list; +} + +static void bdrv_latency_histogram_stats(BlockLatencyHistogram *hist, + bool *not_null, + BlockLatencyHistogramInfo **info) +{ + *not_null = hist->bins != NULL; + if (*not_null) { + *info = g_new0(BlockLatencyHistogramInfo, 1); + + (*info)->boundaries = uint64_list(hist->boundaries, hist->nbins - 1); + (*info)->bins = uint64_list(hist->bins, hist->nbins); + } +} + static void bdrv_query_blk_stats(BlockDeviceStats *ds, BlockBackend *blk) { BlockAcctStats *stats = blk_get_stats(blk); @@ -459,6 +490,16 @@ static void bdrv_query_blk_stats(BlockDeviceStats *ds, BlockBackend *blk) dev_stats->avg_wr_queue_depth = block_acct_queue_depth(ts, BLOCK_ACCT_WRITE); } + + bdrv_latency_histogram_stats(&stats->latency_histogram[BLOCK_ACCT_READ], + &ds->has_x_rd_latency_histogram, + &ds->x_rd_latency_histogram); + bdrv_latency_histogram_stats(&stats->latency_histogram[BLOCK_ACCT_WRITE], + &ds->has_x_wr_latency_histogram, + &ds->x_wr_latency_histogram); + bdrv_latency_histogram_stats(&stats->latency_histogram[BLOCK_ACCT_FLUSH], + &ds->has_x_flush_latency_histogram, + &ds->x_flush_latency_histogram); } static BlockStats *bdrv_query_bds_stats(BlockDriverState *bs, diff --git a/blockdev.c b/blockdev.c index 5048e73aba..c31bf3d98d 100644 --- a/blockdev.c +++ b/blockdev.c @@ -4239,6 +4239,49 @@ void qmp_x_blockdev_set_iothread(const char *node_name, StrOrNull *iothread, aio_context_release(old_context); } +void qmp_x_block_latency_histogram_set( + const char *device, + bool has_boundaries, uint64List *boundaries, + bool has_boundaries_read, uint64List *boundaries_read, + bool has_boundaries_write, uint64List *boundaries_write, + bool has_boundaries_flush, uint64List *boundaries_flush, + Error **errp) +{ + BlockBackend *blk = blk_by_name(device); + BlockAcctStats *stats; + + if (!blk) { + error_setg(errp, "Device '%s' not found", device); + return; + } + stats = blk_get_stats(blk); + + if (!has_boundaries && !has_boundaries_read && !has_boundaries_write && + !has_boundaries_flush) + { + block_latency_histograms_clear(stats); + return; + } + + if (has_boundaries || has_boundaries_read) { + block_latency_histogram_set( + stats, BLOCK_ACCT_READ, + has_boundaries_read ? boundaries_read : boundaries); + } + + if (has_boundaries || has_boundaries_write) { + block_latency_histogram_set( + stats, BLOCK_ACCT_WRITE, + has_boundaries_write ? boundaries_write : boundaries); + } + + if (has_boundaries || has_boundaries_flush) { + block_latency_histogram_set( + stats, BLOCK_ACCT_FLUSH, + has_boundaries_flush ? boundaries_flush : boundaries); + } +} + QemuOptsList qemu_common_drive_opts = { .name = "drive", .head = QTAILQ_HEAD_INITIALIZER(qemu_common_drive_opts.head), diff --git a/qapi/block-core.json b/qapi/block-core.json index f374be4c11..1088ab0c78 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -453,6 +453,106 @@ 'data': {'*name': 'str', 'count': 'int', 'granularity': 'uint32', 'status': 'DirtyBitmapStatus'} } +## +# @BlockLatencyHistogramInfo: +# +# Block latency histogram. +# +# @boundaries: list of interval boundary values in nanoseconds, all greater +# than zero and in ascending order. +# For example, the list [10, 50, 100] produces the following +# histogram intervals: [0, 10), [10, 50), [50, 100), [100, +inf). +# +# @bins: list of io request counts corresponding to histogram intervals. +# len(@bins) = len(@boundaries) + 1 +# For the example above, @bins may be something like [3, 1, 5, 2], +# and corresponding histogram looks like: +# +# 5| * +# 4| * +# 3| * * +# 2| * * * +# 1| * * * * +# +------------------ +# 10 50 100 +# +# Since: 2.12 +## +{ 'struct': 'BlockLatencyHistogramInfo', + 'data': {'boundaries': ['uint64'], 'bins': ['uint64'] } } + +## +# @x-block-latency-histogram-set: +# +# Manage read, write and flush latency histograms for the device. +# +# If only @device parameter is specified, remove all present latency histograms +# for the device. Otherwise, add/reset some of (or all) latency histograms. +# +# @device: device name to set latency histogram for. +# +# @boundaries: list of interval boundary values (see description in +# BlockLatencyHistogramInfo definition). If specified, all +# latency histograms are removed, and empty ones created for all +# io types with intervals corresponding to @boundaries (except for +# io types, for which specific boundaries are set through the +# following parameters). +# +# @boundaries-read: list of interval boundary values for read latency +# histogram. If specified, old read latency histogram is +# removed, and empty one created with intervals +# corresponding to @boundaries-read. The parameter has higher +# priority then @boundaries. +# +# @boundaries-write: list of interval boundary values for write latency +# histogram. +# +# @boundaries-flush: list of interval boundary values for flush latency +# histogram. +# +# Returns: error if device is not found or any boundary arrays are invalid. +# +# Since: 2.12 +# +# Example: set new histograms for all io types with intervals +# [0, 10), [10, 50), [50, 100), [100, +inf): +# +# -> { "execute": "block-latency-histogram-set", +# "arguments": { "device": "drive0", +# "boundaries": [10, 50, 100] } } +# <- { "return": {} } +# +# Example: set new histogram only for write, other histograms will remain +# not changed (or not created): +# +# -> { "execute": "block-latency-histogram-set", +# "arguments": { "device": "drive0", +# "boundaries-write": [10, 50, 100] } } +# <- { "return": {} } +# +# Example: set new histograms with the following intervals: +# read, flush: [0, 10), [10, 50), [50, 100), [100, +inf) +# write: [0, 1000), [1000, 5000), [5000, +inf) +# +# -> { "execute": "block-latency-histogram-set", +# "arguments": { "device": "drive0", +# "boundaries": [10, 50, 100], +# "boundaries-write": [1000, 5000] } } +# <- { "return": {} } +# +# Example: remove all latency histograms: +# +# -> { "execute": "block-latency-histogram-set", +# "arguments": { "device": "drive0" } } +# <- { "return": {} } +## +{ 'command': 'x-block-latency-histogram-set', + 'data': {'device': 'str', + '*boundaries': ['uint64'], + '*boundaries-read': ['uint64'], + '*boundaries-write': ['uint64'], + '*boundaries-flush': ['uint64'] } } + ## # @BlockInfo: # @@ -733,6 +833,12 @@ # @timed_stats: Statistics specific to the set of previously defined # intervals of time (Since 2.5) # +# @x_rd_latency_histogram: @BlockLatencyHistogramInfo. (Since 2.12) +# +# @x_wr_latency_histogram: @BlockLatencyHistogramInfo. (Since 2.12) +# +# @x_flush_latency_histogram: @BlockLatencyHistogramInfo. (Since 2.12) +# # Since: 0.14.0 ## { 'struct': 'BlockDeviceStats', @@ -745,7 +851,10 @@ 'failed_flush_operations': 'int', 'invalid_rd_operations': 'int', 'invalid_wr_operations': 'int', 'invalid_flush_operations': 'int', 'account_invalid': 'bool', 'account_failed': 'bool', - 'timed_stats': ['BlockDeviceTimedStats'] } } + 'timed_stats': ['BlockDeviceTimedStats'], + '*x_rd_latency_histogram': 'BlockLatencyHistogramInfo', + '*x_wr_latency_histogram': 'BlockLatencyHistogramInfo', + '*x_flush_latency_histogram': 'BlockLatencyHistogramInfo' } } ## # @BlockStats: -- cgit v1.2.3-55-g7522