[libvirt] [PATCHv3 08/10] threshold: scrape threshold data from QMP

Eric Blake eblake at redhat.com
Mon Jun 22 23:06:44 UTC 2015


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 at redhat.com>
---
 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




More information about the libvir-list mailing list