
On Fri, Jan 16, 2009 at 03:46:01PM -0500, john cooper wrote:
We have found certain application scenarios where overriding of the default qemu host cache mode provides a substantial improvement in guest performance. In particular, disabling host caching of the file/dev backing a guest drive. A summary of performance metrics may be found below.
Attached is a simple patch which adds general support for a "cache=*" attribute to be passed to qemu from libvirt as part of a xml <driver> element. It is parsed in a domain's xml definition as well as being generated during output of the same. For example:
<disk type='file' device='disk'> <source file='/guest_roots/fc8.root'/> <target dev='hda'/> <driver name='qemu' type='qwerty' cache='none'/> </disk>
where both the existing "type=*" and added "cache=*" attributes are optional and independent.
That scheme looks good. FYI, I have a patch ready which supports the existing driver name & type attributes for QEMU/KVM, as per existing usage in Xen driver
domain_conf.c | 32 +++++++++++++++++++++++++------- domain_conf.h | 15 +++++++++++++++ qemu_conf.c | 32 ++++++++++++++++++++++++++++----
A couple of extra things needed - Addition to tests/qemuxml2argvtest.c to validate the XML to struct to QEMU ARGV conversion. - Addition to tests/qemuxml2xmltest.c to validate XML to struct to XML round-trip conversions. - Addition to the docs/libvirt.rng XML schema.
3 files changed, 68 insertions(+), 11 deletions(-) ================================================================= --- a/src/domain_conf.h +++ b/src/domain_conf.h @@ -80,6 +80,20 @@ enum virDomainDiskBus { VIR_DOMAIN_DISK_BUS_LAST };
+/* summary of all possible host open file cache modes + */ +typedef enum { + VIR_DOMAIN_DISK_CACHE_DEFAULT = 0, + VIR_DOMAIN_DISK_CACHE_DISABLE, + VIR_DOMAIN_DISK_CACHE_WRITEBACK, + VIR_DOMAIN_DISK_CACHE_WRITETHRU, + } host_cachemode;
Can you rename this typedef to virDomainDiskCache, and add a sentinal value VIR_DOMAIN_DISK_CACHE_LAST
+ +typedef struct { + char *tag; + host_cachemode idx; + } cache_map_t;
This 2nd struct is not required.
/* Stores the virtual disk configuration */ typedef struct _virDomainDiskDef virDomainDiskDef; typedef virDomainDiskDef *virDomainDiskDefPtr; @@ -91,6 +105,7 @@ struct _virDomainDiskDef { char *dst; char *driverName; char *driverType; + host_cachemode cachemode;
Our code policy is to use 'int' when enums are used, since enums have no fixed size.
--- a/src/qemu_conf.c +++ b/src/qemu_conf.c @@ -616,6 +616,26 @@ qemudNetworkIfaceConnect(virConnectPtr c return NULL; }
+/* map from internal cache mode to qemu cache arg text + */ +static cache_map_t qemu_cache_map[] = { + {"none", VIR_DOMAIN_DISK_CACHE_DISABLE}, + {"writeback", VIR_DOMAIN_DISK_CACHE_WRITEBACK}, + {"writethrough", VIR_DOMAIN_DISK_CACHE_WRITETHRU}, + {NULL}}; + +/* return qemu arg text * corresponding to idx if found, NULL otherwise + */ +static inline char *qemu_cache_mode(host_cachemode idx) +{ + int i; + + for (i = 0; qemu_cache_map[i].tag; ++i) + if (qemu_cache_map[i].idx == idx) + return (qemu_cache_map[i].tag); + return (NULL); +}
This static map & function are not required. Instead the int to char * conversion (and its reverse) are automatically generated through use of our built-in enum support macros In the domain_conf.h header file add VIR_ENUM_DECL(virDomainDiskCache) And then in the domain_conf.c file the impl is provided by VIR_ENUM_IMPL(virDomainDiskCache, VIR_DOMAIN_DISK_CACHE_LAST, "default", "none", "writeback", "writethrough"); This guarentees (with a compile check) that there is the correct number of constant strings, to match the enum values. You have two generated functions that can be used in the XML parsing/formatting calls int virDomainDiskCacheTypeFromString(const char *type); const char *virDomainDiskCacheTypeFromString(int type);
static int qemudBuildCommandLineChrDevStr(virDomainChrDefPtr dev, char *buf, int buflen) @@ -946,6 +966,8 @@ int qemudBuildCommandLine(virConnectPtr virDomainDiskDefPtr disk = vm->def->disks[i]; int idx = virDiskNameToIndex(disk->dst); const char *bus = virDomainDiskQEMUBusTypeToString(disk->bus); + int nc; + char *cachemode;
if (disk->bus == VIR_DOMAIN_DISK_BUS_USB) { if (disk->device == VIR_DOMAIN_DISK_DEVICE_DISK) { @@ -980,15 +1002,17 @@ int qemudBuildCommandLine(virConnectPtr break; }
- snprintf(opt, PATH_MAX, "file=%s,if=%s,%sindex=%d%s%s", + nc = snprintf(opt, PATH_MAX, "file=%s,if=%s,%sindex=%d%s", disk->src ? disk->src : "", bus, media ? media : "", idx, bootable && disk->device == VIR_DOMAIN_DISK_DEVICE_DISK - ? ",boot=on" : "", - disk->shared && ! disk->readonly - ? ",cache=off" : ""); + ? ",boot=on" : ""); + cachemode = qemu_cache_mode(disk->cachemode); + if (cachemode || disk->shared && !disk->readonly) + snprintf(opt + nc, PATH_MAX - nc, ",cache=%s", + cachemode ? cachemode : "off");
There have been two differnet syntaxes supported in QEMU for caching, so we need to detect this and switch between them. Originally there was just cache=on & cache=off (writethrough and no caching) Now it supports cache=writeback, cache=writethrough and cache=none|off So, if we detect the earlier syntax we need to raise an error if the user requested writeback (or translate it to 'cache=off' for safety sake).
ADD_ARG_LIT("-drive"); ADD_ARG_LIT(opt); ================================================================= --- a/src/domain_conf.c +++ b/src/domain_conf.c @@ -528,6 +528,16 @@ int virDomainDiskCompare(virDomainDiskDe
#ifndef PROXY + +/* map from xml cache tag to internal cache mode + */ +static cache_map_t cache_map[] = { + {"none", VIR_DOMAIN_DISK_CACHE_DISABLE}, + {"off", VIR_DOMAIN_DISK_CACHE_DISABLE}, + {"writeback", VIR_DOMAIN_DISK_CACHE_WRITEBACK}, + {"writethrough", VIR_DOMAIN_DISK_CACHE_WRITETHRU}, + {NULL}}; +
This chunk is not required, because the VIR_ENUM code takes care of this.
/* Parse the XML definition for a disk * @param node XML nodeset to parse for disk definition */ @@ -543,6 +553,8 @@ virDomainDiskDefParseXML(virConnectPtr c char *source = NULL; char *target = NULL; char *bus = NULL; + char *cachetag = NULL; + int i;
if (VIR_ALLOC(def) < 0) { virDomainReportError(conn, VIR_ERR_NO_MEMORY, NULL); @@ -592,6 +604,14 @@ virDomainDiskDefParseXML(virConnectPtr c (xmlStrEqual(cur->name, BAD_CAST "driver"))) { driverName = virXMLPropString(cur, "name"); driverType = virXMLPropString(cur, "type"); + if (cachetag = virXMLPropString(cur, "cache")) { + for (i = 0; cache_map[i].tag; ++i) + if (!strcmp(cache_map[i].tag, cachetag)) { + def->cachemode = cache_map[i].idx; + break; + } + VIR_FREE(cachetag); + }
Indentation got a little mixed up here . The for() loop is can be replaced by a call to virDomainDiskCacheTypeFromString.
} else if (xmlStrEqual(cur->name, BAD_CAST "readonly")) { def->readonly = 1; } else if (xmlStrEqual(cur->name, BAD_CAST "shareable")) { @@ -2620,14 +2640,12 @@ virDomainDiskDefFormat(virConnectPtr con type, device);
if (def->driverName) { + virBufferVSprintf(buf, " <driver name='%s'", def->driverName); if (def->driverType) - virBufferVSprintf(buf, - " <driver name='%s' type='%s'/>\n", - def->driverName, def->driverType); - else - virBufferVSprintf(buf, - " <driver name='%s'/>\n", - def->driverName); + virBufferVSprintf(buf, " type='%s'", def->driverType); + if (def->cachemode) + virBufferVSprintf(buf, " cache='%s'", cache_map[def->cachemode].tag);
The virDomainDiskCacheTypeToString() method should be used for this instead Daniel -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://ovirt.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|