On 09/23/2015 11:24 PM, Jim Fehlig wrote:
Joao Martins wrote:
> Introduce initial support for domainBlockStats API call that
> allow us to query block device statistics. openstack nova
> uses this API call to query block statistics, alongside
> virDomainMemoryStats and virDomainInterfaceStats. Note that
> this patch only introduces it for VBD for starters. QDisk
> will come in a separate patch series.
>
> A new statistics data structure is introduced to fit common
> statistics among others specific to the underlying block
> backends. For the VBD statistics on linux these are exported
> via sysfs on the path:
>
> "/sys/bus/xen-backend/devices/vbd-<domid>-<devid>/statistics"
>
> To calculate the block devid two libxl functions were ported
> (libxlDiskPathMatches and libxlDiskPathParse) to libvirt to
> make sure the devid is calculate in the same way as libxl.
> Each backend implements its own function to extract statistics
> and let there be handled the different platforms. An alternative
> would be to reuse libvirt xen driver function.
>
> VBD stats are exposed in reqs and number of sectors from
> blkback, and it's up to us to convert it to sector sizes.
> The sector size is gathered through xenstore in the device
> backend entry "physical-sector-size". This adds up an extra
> dependency namely of xenstore for doing the xs_read.
>
> BlockStatsFlags variant is also implemented which has the
> added benefit of getting the number of flush requests.
>
> Signed-off-by: Joao Martins <joao.m.martins(a)oracle.com>
> ---
> configure.ac | 2 +-
> src/libxl/libxl_driver.c | 420 +++++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 421 insertions(+), 1 deletion(-)
>
> diff --git a/configure.ac b/configure.ac
> index ef7fbdb..56fb266 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -896,7 +896,7 @@ if test "$with_libxl" != "no" ; then
> LIBS="$LIBS $LIBXL_LIBS"
> AC_CHECK_LIB([xenlight], [libxl_ctx_alloc], [
> with_libxl=yes
> - LIBXL_LIBS="$LIBXL_LIBS -lxenlight -lxenctrl"
> + LIBXL_LIBS="$LIBXL_LIBS -lxenlight -lxenctrl -lxenstore"
> ],[
> if test "$with_libxl" = "yes"; then
> fail=1
> diff --git a/src/libxl/libxl_driver.c b/src/libxl/libxl_driver.c
> index dc83083..fd952a3 100644
> --- a/src/libxl/libxl_driver.c
> +++ b/src/libxl/libxl_driver.c
> @@ -59,6 +59,7 @@
> #include "network/bridge_driver.h"
> #include "locking/domain_lock.h"
> #include "virstats.h"
> +#include <xenstore.h>
>
> #define VIR_FROM_THIS VIR_FROM_LIBXL
>
> @@ -75,6 +76,7 @@ VIR_LOG_INIT("libxl.libxl_driver");
> #define LIBXL_CONFIG_FORMAT_SEXPR "xen-sxpr"
>
> #define LIBXL_NB_TOTAL_CPU_STAT_PARAM 1
> +#define LIBXL_NB_TOTAL_BLK_STAT_PARAM 6
>
> #define HYPERVISOR_CAPABILITIES "/proc/xen/capabilities"
> #define HYPERVISOR_XENSTORED "/dev/xen/xenstored"
> @@ -103,6 +105,25 @@ struct _libxlOSEventHookInfo {
> int id;
> };
>
> +/* Object used to store disk statistics across multiple xen backends */
> +typedef struct _libxlBlockStats libxlBlockStats;
> +typedef libxlBlockStats *libxlBlockStatsPtr;
> +struct _libxlBlockStats {
> + long long rd_req;
> + long long rd_bytes;
> + long long wr_req;
> + long long wr_bytes;
> + long long f_req;
> +
> + char *backend;
> + union {
> + struct {
> + long long ds_req;
> + long long oo_req;
> + } vbd;
> + } u;
> +};
> +
> /* Function declarations */
> static int
> libxlDomainManagedSaveLoad(virDomainObjPtr vm,
> @@ -4641,6 +4662,403 @@ libxlDomainIsUpdated(virDomainPtr dom)
> }
>
> static int
> +libxlDiskPathMatches(const char *virtpath, const char *devtype,
> + int *index_r, int max_index,
> + int *partition_r, int max_partition)
> +{
> + const char *p;
> + char *ep;
> + int tl, c;
> + long pl;
> +
> + tl = strlen(devtype);
> + if (memcmp(virtpath, devtype, tl))
> + return 0;
> +
> + /* We decode the drive letter as if it were in base 52
> + * with digits a-zA-Z, more or less */
> + *index_r = -1;
> + p = virtpath + tl;
> + for (;;) {
> + c = *p++;
> + if (c >= 'a' && c <= 'z') {
> + c -= 'a';
> + } else {
> + --p;
> + break;
> + }
> + (*index_r)++;
> + (*index_r) *= 26;
> + (*index_r) += c;
> +
> + if (*index_r > max_index)
> + return 0;
> + }
> +
> + if (!*p) {
> + *partition_r = 0;
> + return 1;
> + }
> +
> + if (*p == '0')
> + return 0; /* leading zeroes not permitted in partition number */
> +
> + if (virStrToLong_l(p, &ep, 10, &pl) < 0)
> + return 0;
> +
> + if (pl > max_partition || *ep)
> + return 0;
> +
> + *partition_r = pl;
> + return 1;
> +}
> +
> +static int
> +libxlDiskPathParse(const char *virtpath, int *pdisk, int *ppartition)
> +{
> + int disk, partition;
> + char *ep;
> + unsigned long ul;
> + int chrused;
> +
> + chrused = -1;
> + if ((sscanf(virtpath, "d%ip%i%n", &disk, &partition,
&chrused) >= 2
> + && chrused == strlen(virtpath) && disk < (1<<20)
&& partition < 256)
> + ||
> + libxlDiskPathMatches(virtpath, "xvd",
> + &disk, (1<<20)-1,
> + &partition, 255)) {
> + if (pdisk) *pdisk = disk;
> + if (ppartition) *ppartition = partition;
> + if (disk <= 15 && partition <= 15)
> + return (202 << 8) | (disk << 4) | partition;
> + else
> + return (1 << 28) | (disk << 8) | partition;
> + }
> +
> + errno = virStrToLong_ul(virtpath, &ep, 0, &ul);
> + if (!errno && !*ep && ul <= INT_MAX) {
> + /* FIXME: should parse ul to determine these. */
> + if (pdisk || ppartition)
> + return -1;
> + return ul;
> + }
> +
> + if (libxlDiskPathMatches(virtpath, "hd",
> + &disk, 3,
> + &partition, 63)) {
> + if (pdisk) *pdisk = disk;
> + if (ppartition) *ppartition = partition;
> + return ((disk<2 ? 3 : 22) << 8) | ((disk & 1) << 6) |
partition;
> + }
> + if (libxlDiskPathMatches(virtpath, "sd",
> + &disk, 15,
> + &partition, 15)) {
> + if (pdisk) *pdisk = disk;
> + if (ppartition) *ppartition = partition;
> + return (8 << 8) | (disk << 4) | partition;
> + }
> + return -1;
> +}
> +
> +#define LIBXL_VBD_SECTOR_SIZE 512
> +
> +static int
> +libxlDiskSectorSize(int domid, int devno)
> +{
> + char *path, *val;
> + struct xs_handle *handle;
> + int ret = LIBXL_VBD_SECTOR_SIZE;
> + unsigned int len;
> +
> + handle = xs_daemon_open_readonly();
> + if (!handle) {
> + virReportError(VIR_ERR_INTERNAL_ERROR,
> + "%s", _("cannot read sector size"));
> + return ret;
> + }
> +
> + if (virAsprintf(&path, "/local/domain/%d/device/vbd/%d/backend",
> + domid, devno) < 0)
>
Indentation looks off.
> + goto close;
> +
> + if ((val = xs_read(handle, XBT_NULL, path, &len)) == NULL)
> + goto cleanup;
> +
> + VIR_FREE(path);
> + if (virAsprintf(&path, "%s/physical-sector-size", val) < 0) {
> + VIR_FREE(val);
> + goto close;
> + }
> +
> + VIR_FREE(val);
> + if ((val = xs_read(handle, XBT_NULL, path, &len)) == NULL)
> + goto cleanup;
> +
> + if (sscanf(val, "%d", &ret) != 1)
> + ret = -1;
>
Should ret be set back to LIBXL_VBD_SECTOR_SIZE? '-1' wouldn't be a good
value for the calling function.
Indeed, I should ignore the return error and just do VIR_FREE(val).
> +
> + VIR_FREE(val);
> +
> + cleanup:
> + VIR_FREE(path);
> + close:
> + xs_daemon_close(handle);
> + return ret;
> +}
> +
> +static int
> +libxlDomainBlockStatsVBD(virDomainObjPtr vm,
> + const char *dev,
> + libxlBlockStatsPtr stats)
> +{
> + int ret = -1;
> + int devno = libxlDiskPathParse(dev, NULL, NULL);
> + int size = libxlDiskSectorSize(vm->def->id, devno);
> +#ifdef __linux__
> + char *path, *name, *val;
> + unsigned long long stat;
> +
> + if (VIR_STRDUP(stats->backend, "vbd") < 0) {
> + virReportError(VIR_ERR_OPERATION_INVALID,
> + "%s", _("cannot set backend"));
>
VIR_STRDUP() already reports OOM error, which is about the only one it
can encounter.
OK. Two other issues that I already have fixed for v2: 1) libxlDiskPathParse is
renamed to libxlDiskPathToID which is based on virDiskNameParse (as suggested by
Daniel) resulting in a much simpler function; 2) Added validation devno;
> + return -1;
> + }
> +
> + ret = virAsprintf(&path,
"/sys/bus/xen-backend/devices/vbd-%d-%d/statistics",
> + vm->def->id, devno);
>
The usual pattern is 'if (virAsprintf(...) < 0)'.
OK.
> +
> + if (!virFileExists(path)) {
> + virReportError(VIR_ERR_OPERATION_INVALID,
> + "%s", _("cannot open bus path"));
>
I think the error should be VIR_ERR_OPERATION_FAILED.
OK.
> + goto cleanup;
> + }
> +
> +#define LIBXL_SET_VBDSTAT(FIELD, VAR, MUL) \
>
Indentation is off here. Fails 'make syntax-check' with cppi installed.
> + if ((virAsprintf(&name, "%s/"FIELD, path) < 0) || \
> + (virFileReadAll(name, 256, &val) < 0) || \
> + (sscanf(val, "%llu", &stat) != 1)) { \
> + virReportError(VIR_ERR_OPERATION_INVALID, \
> + _("cannot read %s"), name); \
>
VIR_ERR_OPERATION_FAILED?
OK.
> + goto cleanup; \
> + } \
> + VAR += (stat * MUL); \
> + VIR_FREE(name); \
> + VIR_FREE(val);
> +
> + LIBXL_SET_VBDSTAT("f_req", stats->f_req, 1)
> + LIBXL_SET_VBDSTAT("wr_req", stats->wr_req, 1)
> + LIBXL_SET_VBDSTAT("rd_req", stats->rd_req, 1)
> + LIBXL_SET_VBDSTAT("wr_sect", stats->wr_bytes, size)
> + LIBXL_SET_VBDSTAT("rd_sect", stats->rd_bytes, size)
> +
> + LIBXL_SET_VBDSTAT("ds_req", stats->u.vbd.ds_req, size)
> + LIBXL_SET_VBDSTAT("oo_req", stats->u.vbd.oo_req, 1)
> + ret = 0;
> +
> + cleanup:
> + VIR_FREE(name);
> + VIR_FREE(path);
> + VIR_FREE(val);
>
This will only cleanup 'name' and 'val' from the last invocation of the
LIBXL_SET_VBDSTAT macro. 'name' and 'val' from the prior invocations
will be leaked.
The macro will always free 'name' and 'val' on successfull
(prior?) invocations,
thus only the last one needs to be taken care of isn't it?
> +
> +#undef LIBXL_SET_VBDSTAT
>
Another case of failing 'make syntax-check' with cppi installed.
Sorry about this, I didn't notice the cppi warnings. Same issue will appear
also
on the next patch, which are fixed already for V2.
Thanks!
Joao
Regards,
Jim
> +
> +#else
> + virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
> + "%s", _("platform unsupported"));
> +#endif
> + return ret;
> +}
> +
> +static int
> +libxlDomainBlockStatsGatherSingle(virDomainObjPtr vm,
> + const char *path,
> + libxlBlockStatsPtr stats)
> +{
> + virDomainDiskDefPtr disk;
> + const char *disk_drv;
> + int ret = -1, disk_fmt;
> +
> + if (!(disk = virDomainDiskByName(vm->def, path, false))) {
> + virReportError(VIR_ERR_OPERATION_INVALID,
> + _("invalid path: %s"), path);
> + return ret;
> + }
> +
> + disk_fmt = virDomainDiskGetFormat(disk);
> + if (!(disk_drv = virDomainDiskGetDriver(disk)))
> + disk_drv = "qemu";
> +
> + if (STREQ(disk_drv, "phy")) {
> + if (disk_fmt != VIR_STORAGE_FILE_RAW &&
> + disk_fmt != VIR_STORAGE_FILE_NONE) {
> + virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
> + _("unsupported format %s"),
> + virStorageFileFormatTypeToString(disk_fmt));
> + return ret;
> + }
> +
> + ret = libxlDomainBlockStatsVBD(vm, path, stats);
> + } else {
> + virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
> + _("unsupported disk driver %s"),
> + disk_drv);
> + return ret;
> + }
> + return ret;
> +}
> +
> +static int
> +libxlDomainBlockStatsGather(virDomainObjPtr vm,
> + const char *path,
> + libxlBlockStatsPtr stats)
> +{
> + int ret = -1;
> + if (*path) {
> + if (libxlDomainBlockStatsGatherSingle(vm, path, stats) < 0)
> + return ret;
> + } else {
> + size_t i;
> + for (i = 0; i < vm->def->ndisks; ++i) {
> + if (libxlDomainBlockStatsGatherSingle(vm,
vm->def->disks[i]->dst,
> + stats) < 0)
> + return ret;
> + }
> + }
> + return 0;
> +}
> +
> +static int
> +libxlDomainBlockStats(virDomainPtr dom,
> + const char *path,
> + virDomainBlockStatsPtr stats)
> +{
> + libxlDriverPrivatePtr driver = dom->conn->privateData;
> + virDomainObjPtr vm;
> + libxlBlockStats blkstats;
> + int ret = -1;
> +
> + if (!(vm = libxlDomObjFromDomain(dom)))
> + goto cleanup;
> +
> + if (virDomainBlockStatsEnsureACL(dom->conn, vm->def) < 0)
> + goto cleanup;
> +
> + if (libxlDomainObjBeginJob(driver, vm, LIBXL_JOB_QUERY) < 0)
> + goto cleanup;
> +
> + if (!virDomainObjIsActive(vm)) {
> + virReportError(VIR_ERR_OPERATION_INVALID,
> + "%s", _("domain is not running"));
> + goto endjob;
> + }
> +
> + memset(&blkstats, 0, sizeof(libxlBlockStats));
> + if ((ret = libxlDomainBlockStatsGather(vm, path, &blkstats)) < 0)
> + goto endjob;
> +
> + stats->rd_req = blkstats.rd_req;
> + stats->rd_bytes = blkstats.rd_bytes;
> + stats->wr_req = blkstats.wr_req;
> + stats->wr_bytes = blkstats.wr_bytes;
> + if (STREQ_NULLABLE(blkstats.backend, "vbd"))
> + stats->errs = blkstats.u.vbd.oo_req;
> + else
> + stats->errs = -1;
> +
> + endjob:
> + if (!libxlDomainObjEndJob(driver, vm)) {
> + virObjectUnlock(vm);
> + vm = NULL;
> + }
> +
> + cleanup:
> + if (vm)
> + virObjectUnlock(vm);
> + return ret;
> +}
> +
> +static int
> +libxlDomainBlockStatsFlags(virDomainPtr dom,
> + const char *path,
> + virTypedParameterPtr params,
> + int *nparams,
> + unsigned int flags)
> +{
> + libxlDriverPrivatePtr driver = dom->conn->privateData;
> + virDomainObjPtr vm;
> + libxlBlockStats blkstats;
> + int nstats;
> + int ret = -1;
> +
> + virCheckFlags(VIR_TYPED_PARAM_STRING_OKAY, -1);
> +
> + flags &= ~VIR_TYPED_PARAM_STRING_OKAY;
> +
> + if (!(vm = libxlDomObjFromDomain(dom)))
> + goto cleanup;
> +
> + if (virDomainBlockStatsFlagsEnsureACL(dom->conn, vm->def) < 0)
> + goto cleanup;
> +
> + if (libxlDomainObjBeginJob(driver, vm, LIBXL_JOB_QUERY) < 0)
> + goto cleanup;
> +
> + if (!virDomainObjIsActive(vm)) {
> + virReportError(VIR_ERR_OPERATION_INVALID,
> + "%s", _("domain is not running"));
> + goto endjob;
> + }
> +
> + /* return count of supported stats */
> + if (*nparams == 0) {
> + *nparams = LIBXL_NB_TOTAL_BLK_STAT_PARAM;
> + ret = 0;
> + goto endjob;
> + }
> +
> + memset(&blkstats, 0, sizeof(libxlBlockStats));
> + if ((ret = libxlDomainBlockStatsGather(vm, path, &blkstats)) < 0)
> + goto endjob;
> +
> + nstats = 0;
> +
> +#define LIBXL_BLKSTAT_ASSIGN_PARAM(VAR, NAME) \
> + if (nstats < *nparams && (blkstats.VAR) != -1) {
\
> + if (virTypedParameterAssign(params + nstats, NAME, \
> + VIR_TYPED_PARAM_LLONG, (blkstats.VAR)) < 0)
\
> + goto endjob; \
> + nstats++; \
> + }
> +
> + LIBXL_BLKSTAT_ASSIGN_PARAM(wr_bytes, VIR_DOMAIN_BLOCK_STATS_WRITE_BYTES);
> + LIBXL_BLKSTAT_ASSIGN_PARAM(wr_req, VIR_DOMAIN_BLOCK_STATS_WRITE_REQ);
> +
> + LIBXL_BLKSTAT_ASSIGN_PARAM(rd_bytes, VIR_DOMAIN_BLOCK_STATS_READ_BYTES);
> + LIBXL_BLKSTAT_ASSIGN_PARAM(rd_req, VIR_DOMAIN_BLOCK_STATS_READ_REQ);
> +
> + LIBXL_BLKSTAT_ASSIGN_PARAM(f_req, VIR_DOMAIN_BLOCK_STATS_FLUSH_REQ);
> +
> + if (STREQ_NULLABLE(blkstats.backend, "vbd"))
> + LIBXL_BLKSTAT_ASSIGN_PARAM(u.vbd.oo_req, VIR_DOMAIN_BLOCK_STATS_ERRS);
> +
> + *nparams = nstats;
> +
> +#undef LIBXL_BLKSTAT_ASSIGN_PARAM
> +
> + endjob:
> + if (!libxlDomainObjEndJob(driver, vm)) {
> + virObjectUnlock(vm);
> + vm = NULL;
> + }
> +
> + cleanup:
> + if (vm)
> + virObjectUnlock(vm);
> + return ret;
> +}
> +
> +static int
> libxlDomainInterfaceStats(virDomainPtr dom,
> const char *path,
> virDomainInterfaceStatsPtr stats)
> @@ -5459,6 +5877,8 @@ static virHypervisorDriver libxlHypervisorDriver = {
> #endif
> .nodeGetFreeMemory = libxlNodeGetFreeMemory, /* 0.9.0 */
> .nodeGetCellsFreeMemory = libxlNodeGetCellsFreeMemory, /* 1.1.1 */
> + .domainBlockStats = libxlDomainBlockStats, /* 1.2.20 */
> + .domainBlockStatsFlags = libxlDomainBlockStatsFlags, /* 1.2.20 */
> .domainInterfaceStats = libxlDomainInterfaceStats, /* 1.2.20 */
> .domainMemoryStats = libxlDomainMemoryStats, /* 1.2.20 */
> .domainGetCPUStats = libxlDomainGetCPUStats, /* 1.2.20 */
>