[libvirt] [PATCH v4] libxl: implement virDomainBlockStats
Jim Fehlig
jfehlig at suse.com
Fri Jul 22 21:50:37 UTC 2016
On 07/20/2016 04:48 PM, 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
> would 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 devno libxlDiskPathToID is introduced.
Too bad libxl or libxlutil doesn't provide such a function.
> Each backend implements its own function to extract statistics
> and let there be handled the different platforms.
>
> 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".
>
> 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>
> ---
> Changes since v3:
> - No need for error in libxlDiskSectorSize()
> - Fix an identation issue.
> - Rework cleanup paths on libxlDiskSectorSize()
> - Do not unlock vm if libxlDomainObjEndJob() returns false and
> adjust to the newly introduced virDomainObjEndAPI()
> - Bump version to 2.1.0
>
> Changes since v1:
> - Fix identation issues
> - Set ret to LIBXL_VBD_SECTOR_SIZE
> - Reuse VIR_STRDUP error instead of doing virReportError
> when we fail to set stats->backend
> - Change virAsprintf(...) error checking
> - Change error to VIR_ERR_OPERATION_FAILED when xenstore path
> does not exist and when failing to read stat.
> - Resolve issues with 'make syntax-check' with cppi installed.
> - Remove libxlDiskPathMatches in favor of using virutil
> virDiskNameParse to fetch disk and partition index.
> - Rename libxlDiskPathParse to libxlDiskPathToID and rework
> function to just convert disk and partition index to devno.
> - Bump version to 1.2.22
> ---
> src/libxl/libxl_driver.c | 364 +++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 364 insertions(+)
>
> diff --git a/src/libxl/libxl_driver.c b/src/libxl/libxl_driver.c
> index f153f69..189bfb5 100644
> --- a/src/libxl/libxl_driver.c
> +++ b/src/libxl/libxl_driver.c
> @@ -30,6 +30,7 @@
> #include <math.h>
> #include <libxl.h>
> #include <libxl_utils.h>
> +#include <xenstore.h>
> #include <fcntl.h>
> #include <regex.h>
>
> @@ -72,6 +73,7 @@ VIR_LOG_INIT("libxl.libxl_driver");
> #define LIBXL_DOM_REQ_HALT 4
>
> #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"
> @@ -100,6 +102,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,
> @@ -5031,6 +5052,347 @@ libxlDomainGetJobStats(virDomainPtr dom,
> }
>
> static int
> +libxlDiskPathToID(const char *virtpath)
> +{
> + static char const* drive_prefix[] = {"xvd", "hd", "sd"};
> + int disk, partition, chrused;
> + int fmt, id;
> + char *r;
> + size_t i;
> +
> + fmt = id = -1;
> +
> + /* Find any disk prefixes we know about */
> + for (i = 0; i < ARRAY_CARDINALITY(drive_prefix); i++) {
> + if (STRPREFIX(virtpath, drive_prefix[i]) &&
> + !virDiskNameParse(virtpath, &disk, &partition)) {
> + fmt = i;
> + break;
> + }
> + }
> +
> + /* Handle it same way as xvd */
> + if (fmt < 0 &&
> + (sscanf(virtpath, "d%ip%i%n", &disk, &partition, &chrused) >= 2
> + && chrused == strlen(virtpath)))
> + fmt = 0;
> +
> + /* Test indexes ranges and calculate the device id */
> + switch (fmt) {
> + case 0: /* xvd */
> + if (disk <= 15 && partition <= 15)
> + id = (202 << 8) | (disk << 4) | partition;
> + else if ((disk <= ((1<<20)-1)) || partition <= 255)
> + id = (1 << 28) | (disk << 8) | partition;
> + break;
> + case 1: /* hd */
> + if (disk <= 3 && partition <= 63)
> + id = ((disk < 2 ? 3 : 22) << 8) | ((disk & 1) << 6) | partition;
> + break;
> + case 2: /* sd */
> + if (disk <= 15 && (partition <= 15))
> + id = (8 << 8) | (disk << 4) | partition;
> + break;
> + default:
> + errno = virStrToLong_i(virtpath, &r, 0, &id);
> + if (errno || *r || id > INT_MAX)
> + id = -1;
> + break;
The purpose of the default case is not clear to me. Can you explain that a bit?
IIUC, virtpath would contain values such as xvda, sdc, or /path/to/image.
> + }
> + return id;
> +}
> +
> +#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) {
> + VIR_WARN("cannot read sector size");
> + return ret;
> + }
> +
> + path = val = NULL;
> + if (virAsprintf(&path, "/local/domain/%d/device/vbd/%d/backend",
> + domid, devno) < 0)
> + goto cleanup;
> +
> + if ((val = xs_read(handle, XBT_NULL, path, &len)) == NULL)
> + goto cleanup;
> +
> + VIR_FREE(path);
> + if (virAsprintf(&path, "%s/physical-sector-size", val) < 0)
> + goto cleanup;
> +
> + VIR_FREE(val);
> + if ((val = xs_read(handle, XBT_NULL, path, &len)) == NULL)
> + goto cleanup;
> +
> + if (sscanf(val, "%d", &ret) != 1)
> + ret = LIBXL_VBD_SECTOR_SIZE;
> +
> + cleanup:
> + VIR_FREE(val);
> + VIR_FREE(path);
> + xs_daemon_close(handle);
> + return ret;
> +}
> +
> +static int
> +libxlDomainBlockStatsVBD(virDomainObjPtr vm,
> + const char *dev,
> + libxlBlockStatsPtr stats)
The stats parameter is unused when __linux__ is not defined. Otherwise, the
patch looks fine.
Regards,
Jim
> +{
> + int ret = -1;
> + int devno = libxlDiskPathToID(dev);
> + int size = libxlDiskSectorSize(vm->def->id, devno);
> +#ifdef __linux__
> + char *path, *name, *val;
> + unsigned long long stat;
> +
> + path = name = val = NULL;
> + if (devno < 0) {
> + virReportError(VIR_ERR_OPERATION_INVALID,
> + "%s", _("cannot find device number"));
> + return ret;
> + }
> + if (VIR_STRDUP(stats->backend, "vbd") < 0)
> + return ret;
> +
> + if (virAsprintf(&path, "/sys/bus/xen-backend/devices/vbd-%d-%d/statistics",
> + vm->def->id, devno) < 0)
> + return ret;
> +
> + if (!virFileExists(path)) {
> + virReportError(VIR_ERR_OPERATION_FAILED,
> + "%s", _("cannot open bus path"));
> + goto cleanup;
> + }
> +
> +# define LIBXL_SET_VBDSTAT(FIELD, VAR, MUL) \
> + if ((virAsprintf(&name, "%s/"FIELD, path) < 0) || \
> + (virFileReadAll(name, 256, &val) < 0) || \
> + (sscanf(val, "%llu", &stat) != 1)) { \
> + virReportError(VIR_ERR_OPERATION_FAILED, \
> + _("cannot read %s"), name); \
> + 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);
> +
> +# undef LIBXL_SET_VBDSTAT
> +
> +#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;
> +}
> +
> +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:
> + libxlDomainObjEndJob(driver, vm);
> +
> + cleanup:
> + virDomainObjEndAPI(&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:
> + libxlDomainObjEndJob(driver, vm);
> +
> + cleanup:
> + virDomainObjEndAPI(&vm);
> + return ret;
> +}
> +
> +static int
> libxlConnectDomainEventRegisterAny(virConnectPtr conn, virDomainPtr dom, int eventID,
> virConnectDomainEventGenericCallback callback,
> void *opaque, virFreeCallback freecb)
> @@ -5847,6 +6209,8 @@ static virHypervisorDriver libxlHypervisorDriver = {
> .domainMemoryStats = libxlDomainMemoryStats, /* 1.3.0 */
> .domainGetCPUStats = libxlDomainGetCPUStats, /* 1.3.0 */
> .domainInterfaceStats = libxlDomainInterfaceStats, /* 1.3.2 */
> + .domainBlockStats = libxlDomainBlockStats, /* 2.1.0 */
> + .domainBlockStatsFlags = libxlDomainBlockStatsFlags, /* 2.1.0 */
> .connectDomainEventRegister = libxlConnectDomainEventRegister, /* 0.9.0 */
> .connectDomainEventDeregister = libxlConnectDomainEventDeregister, /* 0.9.0 */
> .domainManagedSave = libxlDomainManagedSave, /* 0.9.2 */
More information about the libvir-list
mailing list