[libvirt] [PATCH RFC 4/7] libxl: implement virDomainBlockStats
Joao Martins
joao.m.martins at oracle.com
Thu Sep 24 10:33:42 UTC 2015
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 at 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 */
>>
More information about the libvir-list
mailing list