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

Peter Krempa pkrempa at redhat.com
Tue Jun 23 13:39:10 UTC 2015


On Mon, Jun 22, 2015 at 17:06:44 -0600, Eric Blake wrote:
> 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.  */

While this is okay in this intermediate part I am not okay with merging
this series without the full support for non-top layers.

If we add a partial impl we will force the users to check whether given
libvirt supports it.

Since I know of only one consumer of this feature which is oVirt and
they are interrested in both data we would increase the complexity of
usefullnes of this feature.

> +    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)

I'll send a series today that will remove this function.
qemuMonitorJSONGetOneBlockStatsInfo will need to do this operation so
that we don't duplicate calls to query-blockstats.


>  {
>      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);

Spurious whitespace change.

>  #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

I think in the future it will be necessary to have the data from
query-named-blocks available for use so I think that this helper should
similarly to qemuMonitorJSONGetAllBlockStatsInfo return a hash of the
useful data, in this case keyed by the node name, and the caller would
do the disk->node matching when it requires the data if it requires it.

> +     * 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

Peter
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: Digital signature
URL: <http://listman.redhat.com/archives/libvir-list/attachments/20150623/2c021c90/attachment-0001.sig>


More information about the libvir-list mailing list