From eee7bd4ecb5a3e83ed47cc89bf0dc4866b4858af Mon Sep 17 00:00:00 2001 From: Joao Martins Date: Tue, 26 Jul 2016 00:45:14 +0100 Subject: [PATCH] libxl: implement virDomainBlockStats 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--/statistics" To calculate the block devno libxlDiskPathToID is introduced. Each backend implements its own function to extract statistics, allowing support for multiple backends and 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 --- src/libxl/libxl_driver.c | 370 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 370 insertions(+) diff --git a/src/libxl/libxl_driver.c b/src/libxl/libxl_driver.c index f153f69953..cb501cf4a8 100644 --- a/src/libxl/libxl_driver.c +++ b/src/libxl/libxl_driver.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -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, @@ -5030,6 +5051,353 @@ libxlDomainGetJobStats(virDomainPtr dom, return ret; } +static int +libxlDiskPathToID(const char *virtpath) +{ + static char const* drive_prefix[] = {"xvd", "hd", "sd"}; + int disk, partition, chrused; + int fmt, id; + 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: /* invalid */ + break; + } + 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; +} + +#ifdef __linux__ +static int +libxlDomainBlockStatsVBD(virDomainObjPtr vm, + const char *dev, + libxlBlockStatsPtr stats) +{ + int ret = -1; + int devno = libxlDiskPathToID(dev); + int size; + 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; + } + + size = libxlDiskSectorSize(vm->def->id, devno); + + 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 + + return ret; +} +#else +static int +libxlDomainBlockStatsVBD(virDomainObjPtr vm ATTRIBUTE_UNUSED, + const char *dev ATTRIBUTE_UNUSED, + libxlBlockStatsPtr stats ATTRIBUTE_UNUSED) +{ + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, + "%s", _("platform unsupported")); + return -1; +} +#endif + +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, disk->dst, 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, @@ -5847,6 +6215,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 */ -- GitLab