Expose threshold information by collecting the information from QMP commands. qemu 2.3 has a way to get threshold information on all elements of a block chain, but only when node names are used - what's worse, the threshold information is only provided by 'query-named-block-nodes', but the mapping between devices and nodes is only provided by 'query-blockstats'. Rather than manage node names ourselves, we rely on qemu 2.4 doing auto naming; then when collecting the stats, we make a pass through both query functions. I chose to go with the naive O(n^2) mapping algorithm; if it turns out to be slow in practice on a guest with lots of nodes, we can enhance it via a sorted list or hash table lookup. * src/qemu/qemu_monitor.h (qemuMonitorBlockStatsUpdateThreshold): New prototype. * src/qemu/qemu_monitor.c (qemuMonitorBlockStatsUpdateThreshold): New function. * src/qemu/qemu_monitor_json.h (qemuMonitorJSONBlockStatsUpdateThreshold): New prototype. * src/qemu/qemu_monitor_json.c (qemuMonitorJSONDevGetBlockExtent): Populate node name. (qemuMonitorJSONBlockStatsUpdateThreshold) (qemuMonitorJSONBlockStatsUpdateOneThreshold): Use it to populate threshold data. (qemuMonitorJSONGetOneBlockStatsInfo) (qemuMonitorJSONGetBlockExtent): Update callers. * src/qemu/qemu_driver.c (qemuDomainGetStatsOneBlock): Expose threshold data. Signed-off-by: Eric Blake <eblake@xxxxxxxxxx> --- src/qemu/qemu_driver.c | 12 ++++- src/qemu/qemu_monitor.c | 13 ++++++ src/qemu/qemu_monitor.h | 4 ++ src/qemu/qemu_monitor_json.c | 102 ++++++++++++++++++++++++++++++++++++++----- src/qemu/qemu_monitor_json.h | 2 + 5 files changed, 121 insertions(+), 12 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 25bc76d..72e256b 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -19282,6 +19282,12 @@ qemuDomainGetStatsOneBlock(virQEMUDriverPtr driver, QEMU_ADD_BLOCK_PARAM_LL(record, maxparams, block_idx, "fl.times", entry->flush_total_times); + /* TODO: Until we can set thresholds on backing elements, we only + * report the threshold on the active layer. */ + if (!backing_idx) + QEMU_ADD_BLOCK_PARAM_ULL(record, maxparams, block_idx, + "write-threshold", entry->write_threshold); + QEMU_ADD_BLOCK_PARAM_ULL(record, maxparams, block_idx, "allocation", entry->wr_highest_offset); @@ -19323,9 +19329,13 @@ qemuDomainGetStatsBlock(virQEMUDriverPtr driver, qemuDomainObjEnterMonitor(driver, dom); rc = qemuMonitorGetAllBlockStatsInfo(priv->mon, &stats, visitBacking); - if (rc >= 0) + if (rc >= 0) { ignore_value(qemuMonitorBlockStatsUpdateCapacity(priv->mon, stats, visitBacking)); + if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_AUTO_NODE_NAMES)) + ignore_value(qemuMonitorBlockStatsUpdateThreshold(priv->mon, + stats)); + } if (qemuDomainObjExitMonitor(driver, dom) < 0) goto cleanup; diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index 86dc4be..a3ad740 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -1838,6 +1838,19 @@ qemuMonitorBlockStatsUpdateCapacity(qemuMonitorPtr mon, } +/* Updates "stats" to fill write threshold of the image */ +int +qemuMonitorBlockStatsUpdateThreshold(qemuMonitorPtr mon, + virHashTablePtr stats) +{ + VIR_DEBUG("stats=%p", stats); + + QEMU_CHECK_MONITOR_JSON(mon); + + return qemuMonitorJSONBlockStatsUpdateThreshold(mon, stats); +} + + int qemuMonitorGetBlockExtent(qemuMonitorPtr mon, const char *dev_name, diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h index c0ea2ee..541f774 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -395,6 +395,10 @@ int qemuMonitorBlockStatsUpdateCapacity(qemuMonitorPtr mon, bool backingChain) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); +int qemuMonitorBlockStatsUpdateThreshold(qemuMonitorPtr mon, + virHashTablePtr stats) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); + int qemuMonitorGetBlockExtent(qemuMonitorPtr mon, const char *dev_name, unsigned long long *extent); diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index 32b2719..885b6f4 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -1668,9 +1668,13 @@ typedef enum { } qemuMonitorBlockExtentError; +/* Get the highest written extent. Additionally, if NODE is not null, + * and a node name is associated with the extent, then return that + * name; but failure to get a node name is not fatal. */ static int qemuMonitorJSONDevGetBlockExtent(virJSONValuePtr dev, - unsigned long long *extent) + unsigned long long *extent, + char **node) { virJSONValuePtr stats; virJSONValuePtr parent; @@ -1678,6 +1682,11 @@ qemuMonitorJSONDevGetBlockExtent(virJSONValuePtr dev, if ((parent = virJSONValueObjectGetObject(dev, "parent")) == NULL) return QEMU_MONITOR_BLOCK_EXTENT_ERROR_NOPARENT; + if (node) { + const char *name = virJSONValueObjectGetString(parent, "node-name"); + ignore_value(VIR_STRDUP_QUIET(*node, name)); + } + if ((stats = virJSONValueObjectGetObject(parent, "stats")) == NULL) return QEMU_MONITOR_BLOCK_EXTENT_ERROR_NOSTATS; @@ -1725,18 +1734,23 @@ qemuMonitorJSONGetOneBlockStatsInfo(virJSONValuePtr dev, goto cleanup; \ } \ } - QEMU_MONITOR_BLOCK_STAT_GET("rd_bytes", bstats->rd_bytes, true); - QEMU_MONITOR_BLOCK_STAT_GET("wr_bytes", bstats->wr_bytes, true); - QEMU_MONITOR_BLOCK_STAT_GET("rd_operations", bstats->rd_req, true); - QEMU_MONITOR_BLOCK_STAT_GET("wr_operations", bstats->wr_req, true); - QEMU_MONITOR_BLOCK_STAT_GET("rd_total_time_ns", bstats->rd_total_times, false); - QEMU_MONITOR_BLOCK_STAT_GET("wr_total_time_ns", bstats->wr_total_times, false); - QEMU_MONITOR_BLOCK_STAT_GET("flush_operations", bstats->flush_req, false); - QEMU_MONITOR_BLOCK_STAT_GET("flush_total_time_ns", bstats->flush_total_times, false); + QEMU_MONITOR_BLOCK_STAT_GET("rd_bytes", bstats->rd_bytes, true); + QEMU_MONITOR_BLOCK_STAT_GET("wr_bytes", bstats->wr_bytes, true); + QEMU_MONITOR_BLOCK_STAT_GET("rd_operations", bstats->rd_req, true); + QEMU_MONITOR_BLOCK_STAT_GET("wr_operations", bstats->wr_req, true); + QEMU_MONITOR_BLOCK_STAT_GET("rd_total_time_ns", bstats->rd_total_times, + false); + QEMU_MONITOR_BLOCK_STAT_GET("wr_total_time_ns", bstats->wr_total_times, + false); + QEMU_MONITOR_BLOCK_STAT_GET("flush_operations", bstats->flush_req, + false); + QEMU_MONITOR_BLOCK_STAT_GET("flush_total_time_ns", + bstats->flush_total_times, false); #undef QEMU_MONITOR_BLOCK_STAT_GET /* it's ok to not have this information here. Just skip silently. */ - qemuMonitorJSONDevGetBlockExtent(dev, &bstats->wr_highest_offset); + qemuMonitorJSONDevGetBlockExtent(dev, &bstats->wr_highest_offset, + &bstats->allocation_node); if (virHashAddEntry(hash, entry_name, bstats) < 0) goto cleanup; @@ -1937,6 +1951,72 @@ qemuMonitorJSONBlockStatsUpdateCapacity(qemuMonitorPtr mon, } +/* Hash table callback: Best effort routine to update write-threshold + * of a given qemuBlockStatsPtr (payload) using the QMP results for + * all nodes (opaque). */ +static void +qemuMonitorJSONBlockStatsUpdateOneThreshold(void *payload, + const void *entry ATTRIBUTE_UNUSED, + void *opaque) +{ + qemuBlockStatsPtr data = payload; + virJSONValuePtr nodes = opaque; + size_t i; + + if (!data->allocation_node) + return; + for (i = 0; i < virJSONValueArraySize(nodes); i++) { + virJSONValuePtr node = virJSONValueArrayGet(nodes, i); + const char *name = virJSONValueObjectGetString(node, "node-name"); + + if (STREQ_NULLABLE(data->allocation_node, name) && + virJSONValueObjectGetNumberUlong(node, "write_threshold", + &data->write_threshold) == 0) + break; + } +} + + +int +qemuMonitorJSONBlockStatsUpdateThreshold(qemuMonitorPtr mon, + virHashTablePtr stats) +{ + int ret = -1; + int rc; + virJSONValuePtr cmd; + virJSONValuePtr reply = NULL; + virJSONValuePtr devices; + + if (!(cmd = qemuMonitorJSONMakeCommand("query-named-block-nodes", NULL))) + return -1; + if ((rc = qemuMonitorJSONCommand(mon, cmd, &reply)) < 0) + goto cleanup; + if (qemuMonitorJSONCheckError(cmd, reply) < 0) + goto cleanup; + + if (!(devices = virJSONValueObjectGetArray(reply, "return"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("query-named-block-nodes reply was missing list")); + goto cleanup; + } + /* O(n^2) collection pass because we visit the entire nodes list + * for each stats entry. Hopefully there aren't a boat-load of + * nodes to make this noticeably slower. If we need, we can do a + * pre-processing pass over devices to reach O(n log n) (via + * sorted list) or amortized O(n) (via a hash table) layout where + * node name lookup is more efficient. */ + if (virJSONValueArraySize(devices) > 0) + virHashForEach(stats, qemuMonitorJSONBlockStatsUpdateOneThreshold, + devices); + ret = 0; + + cleanup: + virJSONValueFree(cmd); + virJSONValueFree(reply); + return ret; +} + + static int qemuMonitorJSONReportBlockExtentError(qemuMonitorBlockExtentError error) { @@ -2025,7 +2105,7 @@ int qemuMonitorJSONGetBlockExtent(qemuMonitorPtr mon, continue; found = true; - if ((err = qemuMonitorJSONDevGetBlockExtent(dev, extent)) != + if ((err = qemuMonitorJSONDevGetBlockExtent(dev, extent, NULL)) != QEMU_MONITOR_BLOCK_EXTENT_ERROR_OK) { qemuMonitorJSONReportBlockExtentError(err); goto cleanup; diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h index f18295b..2f90b5c 100644 --- a/src/qemu/qemu_monitor_json.h +++ b/src/qemu/qemu_monitor_json.h @@ -77,6 +77,8 @@ int qemuMonitorJSONGetAllBlockStatsInfo(qemuMonitorPtr mon, int qemuMonitorJSONBlockStatsUpdateCapacity(qemuMonitorPtr mon, virHashTablePtr stats, bool backingChain); +int qemuMonitorJSONBlockStatsUpdateThreshold(qemuMonitorPtr mon, + virHashTablePtr stats); int qemuMonitorJSONGetBlockExtent(qemuMonitorPtr mon, const char *dev_name, unsigned long long *extent); -- 2.4.3 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list