[libvirt] [PATCH 00/21] Initial support for resctrl (CAT)

This is an initial support for resctrl, so that domains can make use of Cache Allocation Technology (🐈). After bunch of initial discussions I followed most of those things except one detail. In order to distinguish different allocations while minimizing the number of them at the same time, I changed the XML design, but only slightly. See docs in patch 17 for detail. There is still much missing, but all is designed in a way that should not collied with the future additions. Missing features include: - After allocation is created the file should be re-read and parsed into the al the allocation so that the masks reflect what kernel actually uses (just in case there was a change) - When formatting live XML, used masks for allocations should be formatted as well - Each <cache/> should support shared allocations, for now every single allocation is exclusive only - Suuport for different backing, particularly if allocation has type='both' (code as well as data) and the host supports CDP (Code Data Partitioning), meaning it can allocate code and data portions separately the code currently fails even though it is possible to convert such allocation. - APIs for manipulation with various resctrl groups. What's needed is reading and setting values of the default group as well as any cachetune of a domain. Martin Kletzander (21): Only output initialized capabilities with VIR_TEST_DEBUG > 1 util: Introduce virPrettySize util: Make prefix optional in virBitampString util: Rename virBitmapString to virBitmapToString util: Rename virBitmapDataToString to virBitmapDataFormat util: Don't output too many zeros from virBitmapToString util: Introduce virBitmapNewString util: Reintroduce virBitmapSubtract util: Introduce virBitmapShrink conf: Sort cache banks in capabilities XML conf: Format cache banks in capabilities with virPrettySize resctrl: Instantiate all resctrl information at once tests: Remove executable bits on plain data files tests: Change some schemata for the default group caps2xml resctrl-skx-twocaches resctrl: Add functions to work with resctrl allocations conf: cachetune tests: Minor adjustments for test data tests: Add virresctrltest qemu: Add support for resctrl docs: Add CAT (resctrl) support into news.xml docs/formatdomain.html.in | 24 + docs/news.xml | 11 + docs/schemas/domaincommon.rng | 32 + po/POTFILES.in | 1 + src/Makefile.am | 2 +- src/conf/capabilities.c | 113 +- src/conf/capabilities.h | 4 +- src/conf/domain_conf.c | 249 ++++ src/conf/domain_conf.h | 21 + src/libvirt_private.syms | 24 +- src/qemu/qemu_capabilities.c | 4 +- src/qemu/qemu_process.c | 61 +- src/util/virbitmap.c | 118 +- src/util/virbitmap.h | 15 +- src/util/virresctrl.c | 1317 ++++++++++++++++++-- src/util/virresctrl.h | 81 +- src/util/virresctrlpriv.h | 32 + src/util/virutil.c | 50 + src/util/virutil.h | 3 + tests/Makefile.am | 8 +- .../genericxml2xmlindata/generic-cachetune-cdp.xml | 36 + .../generic-cachetune-colliding-allocs.xml | 30 + .../generic-cachetune-colliding-tunes.xml | 32 + .../generic-cachetune-colliding-types.xml | 30 + .../generic-cachetune-small.xml | 29 + tests/genericxml2xmlindata/generic-cachetune.xml | 33 + tests/genericxml2xmltest.c | 10 + tests/testutils.c | 2 +- tests/virbitmaptest.c | 84 +- tests/vircaps2xmldata/linux-caches/system | 1 + .../linux-resctrl-cdp/resctrl/info/L3CODE/cbm_mask | 0 .../resctrl/info/L3CODE/min_cbm_bits | 0 .../resctrl/info/L3CODE/num_closids | 0 .../linux-resctrl-cdp/resctrl/info/L3DATA/cbm_mask | 0 .../resctrl/info/L3DATA/min_cbm_bits | 0 .../resctrl/info/L3DATA/num_closids | 0 .../linux-resctrl-cdp/resctrl/schemata | 4 +- .../resctrl/info/L3/cbm_mask | 1 + .../resctrl/info/L3}/min_cbm_bits | 0 .../resctrl/info/L3/num_closids | 1 + .../linux-resctrl-skx-twocaches/resctrl/schemata | 1 + .../resctrl/some_reservation/schemata | 1 + .../system/cpu/cpu0/cache/index0/id | 1 + .../system/cpu/cpu0/cache/index0/level | 1 + .../system/cpu/cpu0/cache/index0/shared_cpu_list | 1 + .../system/cpu/cpu0/cache/index0/shared_cpu_map | 1 + .../system/cpu/cpu0/cache/index0/size | 1 + .../system/cpu/cpu0/cache/index0/type | 1 + .../system/cpu/cpu0/cache/index1/id} | 0 .../system/cpu/cpu0/cache/index1/level | 1 + .../system/cpu/cpu0/cache/index1/shared_cpu_list | 1 + .../system/cpu/cpu0/cache/index1/shared_cpu_map | 1 + .../system/cpu/cpu0/cache/index1/size | 1 + .../system/cpu/cpu0/cache/index1/type | 1 + .../system/cpu/cpu0/online} | 0 .../system/cpu/cpu0/topology/core_id | 1 + .../system/cpu/cpu0/topology/core_siblings | 1 + .../system/cpu/cpu0/topology/core_siblings_list | 1 + .../system/cpu/cpu0/topology/physical_package_id | 1 + .../system/cpu/cpu0/topology/thread_siblings | 1 + .../system/cpu/cpu0/topology/thread_siblings_list | 1 + .../linux-resctrl-skx-twocaches/system/cpu/online | 1 + .../linux-resctrl-skx-twocaches/system/cpu/present | 1 + .../system/node/node0/cpu0 | 1 + .../system/node/node0/cpulist | 1 + .../system/node/node0/cpumap | 1 + .../system/node/node0/distance | 1 + .../linux-resctrl-skx-twocaches/system/node/online | 1 + .../linux-resctrl-skx/resctrl/empty/schemata | 0 .../linux-resctrl-skx/resctrl/schemata | 2 +- .../vircaps2xmldata/linux-resctrl/resctrl/schemata | 2 +- tests/vircaps2xmldata/vircaps-x86_64-caches.xml | 2 +- .../vircaps2xmldata/vircaps-x86_64-resctrl-cdp.xml | 4 +- ...ml => vircaps-x86_64-resctrl-skx-twocaches.xml} | 7 +- .../vircaps2xmldata/vircaps-x86_64-resctrl-skx.xml | 4 +- tests/vircaps2xmldata/vircaps-x86_64-resctrl.xml | 4 +- tests/vircaps2xmltest.c | 1 + .../resctrl--cachetune/vcpus-0-1.alloc | 1 + .../resctrl--cachetune/vcpus-3.alloc | 1 + .../resctrl-cdp--cachetune-cdp/vcpus-0-1.alloc | 2 + .../resctrl-cdp--cachetune-cdp/vcpus-2.alloc | 1 + .../resctrl-cdp--cachetune-cdp/vcpus-3.alloc | 1 + tests/virresctrldata/resctrl-cdp.schemata | 2 + .../virresctrldata/resctrl-skx-twocaches.schemata | 1 + tests/virresctrldata/resctrl-skx.schemata | 1 + tests/virresctrldata/resctrl.schemata | 1 + tests/virresctrltest.c | 277 ++++ tools/virsh-domain.c | 4 +- tools/virsh-host.c | 2 +- 89 files changed, 2626 insertions(+), 183 deletions(-) create mode 100644 src/util/virresctrlpriv.h create mode 100644 tests/genericxml2xmlindata/generic-cachetune-cdp.xml create mode 100644 tests/genericxml2xmlindata/generic-cachetune-colliding-allocs.xml create mode 100644 tests/genericxml2xmlindata/generic-cachetune-colliding-tunes.xml create mode 100644 tests/genericxml2xmlindata/generic-cachetune-colliding-types.xml create mode 100644 tests/genericxml2xmlindata/generic-cachetune-small.xml create mode 100644 tests/genericxml2xmlindata/generic-cachetune.xml create mode 120000 tests/vircaps2xmldata/linux-caches/system mode change 100755 => 100644 tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3CODE/cbm_mask mode change 100755 => 100644 tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3CODE/min_cbm_bits mode change 100755 => 100644 tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3CODE/num_closids mode change 100755 => 100644 tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3DATA/cbm_mask mode change 100755 => 100644 tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3DATA/min_cbm_bits mode change 100755 => 100644 tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3DATA/num_closids create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/resctrl/info/L3/cbm_mask copy tests/vircaps2xmldata/{linux-resctrl-cdp/resctrl/info/L3CODE => linux-resctrl-skx-twocaches/resctrl/info/L3}/min_cbm_bits (100%) mode change 100755 => 100644 create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/resctrl/info/L3/num_closids create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/resctrl/schemata create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/resctrl/some_reservation/schemata create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/id create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/level create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/shared_cpu_list create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/shared_cpu_map create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/size create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/type copy tests/vircaps2xmldata/{linux-resctrl-cdp/resctrl/info/L3CODE/min_cbm_bits => linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/id} (100%) mode change 100755 => 100644 create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/level create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/shared_cpu_list create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/shared_cpu_map create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/size create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/type copy tests/vircaps2xmldata/{linux-resctrl-cdp/resctrl/info/L3CODE/min_cbm_bits => linux-resctrl-skx-twocaches/system/cpu/cpu0/online} (100%) mode change 100755 => 100644 create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/core_id create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/core_siblings create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/core_siblings_list create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/physical_package_id create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/thread_siblings create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/thread_siblings_list create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/online create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/present create mode 120000 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/node/node0/cpu0 create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/node/node0/cpulist create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/node/node0/cpumap create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/node/node0/distance create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/node/online create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx/resctrl/empty/schemata copy tests/vircaps2xmldata/{vircaps-x86_64-resctrl-skx.xml => vircaps-x86_64-resctrl-skx-twocaches.xml} (68%) create mode 100644 tests/virresctrldata/resctrl--cachetune/vcpus-0-1.alloc create mode 100644 tests/virresctrldata/resctrl--cachetune/vcpus-3.alloc create mode 100644 tests/virresctrldata/resctrl-cdp--cachetune-cdp/vcpus-0-1.alloc create mode 100644 tests/virresctrldata/resctrl-cdp--cachetune-cdp/vcpus-2.alloc create mode 100644 tests/virresctrldata/resctrl-cdp--cachetune-cdp/vcpus-3.alloc create mode 100644 tests/virresctrldata/resctrl-cdp.schemata create mode 100644 tests/virresctrldata/resctrl-skx-twocaches.schemata create mode 100644 tests/virresctrldata/resctrl-skx.schemata create mode 100644 tests/virresctrldata/resctrl.schemata create mode 100644 tests/virresctrltest.c -- 2.15.0

Currenty virTestInit() outputs all capabilities that it created when running with VIR_TEST_DEBUG=1. Since this is quite a lot of output for every call of this function (and it is not needed until debugging a really deep-down issue) let's just output the info when VIR_TEST_DEBUG is strictly greater than 1. Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- tests/testutils.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testutils.c b/tests/testutils.c index 915cd3b7925b..a991562adffe 100644 --- a/tests/testutils.c +++ b/tests/testutils.c @@ -1208,7 +1208,7 @@ virCapsPtr virTestGenericCapsInit(void) goto error; - if (virTestGetDebug()) { + if (virTestGetDebug() > 1) { char *caps_str; caps_str = virCapabilitiesFormatXML(caps); -- 2.15.0

On Mon, Nov 13, 2017 at 09:50:16AM +0100, Martin Kletzander wrote:
Currenty virTestInit() outputs all capabilities that it created when running with VIR_TEST_DEBUG=1. Since this is quite a lot of output for every call of this function (and it is not needed until debugging a really deep-down issue) let's just output the info when VIR_TEST_DEBUG is strictly greater than 1.
Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- tests/testutils.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
Reviewed-by: Pavel Hrdina <phrdina@redhat.com>

We can't output better memory sizes if we want to be compatible with libvirt older than the one which introduced /memory/unit, but for new things we can just output nicer capacity to the user if available. And this function enables that. Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- src/libvirt_private.syms | 1 + src/util/virutil.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ src/util/virutil.h | 3 +++ 3 files changed, 54 insertions(+) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 36cd5b55b249..d4bae6150bb8 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2954,6 +2954,7 @@ virParseNumber; virParseOwnershipIds; virParseVersionString; virPipeReadUntilEOF; +virPrettySize; virScaleInteger; virSetBlocking; virSetCloseExec; diff --git a/src/util/virutil.c b/src/util/virutil.c index 170e921920ef..dcfb65262aff 100644 --- a/src/util/virutil.c +++ b/src/util/virutil.c @@ -1993,3 +1993,53 @@ virMemoryMaxValue(bool capped) else return LLONG_MAX; } + + +/** + * virPrettySize + * + * @val: Value in bytes to be shortened + * @unit: unit to be used + * + * Similar to vshPrettyCapacity, but operates on integers and not doubles + * + * Returns shortened value that can be used with @unit. + */ +unsigned long long +virPrettySize(unsigned long long val, const char **unit) +{ + unsigned long long limit = 1024; + + if (val % limit || val == 0) { + *unit = "B"; + return val; + } + limit *= 1024; + if (val % limit) { + *unit = "KiB"; + return val / (limit / 1024); + } + limit *= 1024; + if (val % limit) { + *unit = "MiB"; + return val / (limit / 1024); + } + limit *= 1024; + if (val % limit) { + *unit = "GiB"; + return val / (limit / 1024); + } + limit *= 1024; + if (val % limit) { + *unit = "TiB"; + return val / (limit / 1024); + } + limit *= 1024; + if (val % limit) { + *unit = "PiB"; + return val / (limit / 1024); + } + limit *= 1024; + *unit = "EiB"; + return val / (limit / 1024); +} diff --git a/src/util/virutil.h b/src/util/virutil.h index ff89d1aaaa5f..72e35fc9a607 100644 --- a/src/util/virutil.h +++ b/src/util/virutil.h @@ -222,4 +222,7 @@ unsigned long long virMemoryMaxValue(bool ulong) ATTRIBUTE_NOINLINE; # define VIR_ASSIGN_IS_OVERFLOW(lvalue, rvalue) \ (((lvalue) = (rvalue)) != (rvalue)) +unsigned long long +virPrettySize(unsigned long long val, const char **unit); + #endif /* __VIR_UTIL_H__ */ -- 2.15.0

On 11/13/2017 03:50 AM, Martin Kletzander wrote:
We can't output better memory sizes if we want to be compatible with libvirt older than the one which introduced /memory/unit, but for new things we can just output nicer capacity to the user if available. And this function enables that.
Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- src/libvirt_private.syms | 1 + src/util/virutil.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ src/util/virutil.h | 3 +++ 3 files changed, 54 insertions(+)
Since we have virFormatIntDecimal, why not change the name to be virFormatPrettySize. I'd also support moving it closer to the virFormat* function (both .c and .h), but it's not a "requirement"... Reviewed-by: John Ferlan <jferlan@redhat.com> John

On Mon, Nov 13, 2017 at 01:36:58PM -0500, John Ferlan wrote:
On 11/13/2017 03:50 AM, Martin Kletzander wrote:
We can't output better memory sizes if we want to be compatible with libvirt older than the one which introduced /memory/unit, but for new things we can just output nicer capacity to the user if available. And this function enables that.
Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- src/libvirt_private.syms | 1 + src/util/virutil.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ src/util/virutil.h | 3 +++ 3 files changed, 54 insertions(+)
Since we have virFormatIntDecimal, why not change the name to be virFormatPrettySize. I'd also support moving it closer to the virFormat* function (both .c and .h), but it's not a "requirement"...
Sure, I can do that.
Reviewed-by: John Ferlan <jferlan@redhat.com>
John

Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- src/qemu/qemu_capabilities.c | 4 ++-- src/util/virbitmap.c | 6 ++++-- src/util/virbitmap.h | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/qemu/qemu_capabilities.c b/src/qemu/qemu_capabilities.c index 7cb091056b48..c87feefb3be3 100644 --- a/src/qemu/qemu_capabilities.c +++ b/src/qemu/qemu_capabilities.c @@ -1509,7 +1509,7 @@ int virQEMUCapsParseHelpStr(const char *qemu, qemuCaps, check_yajl) < 0) goto cleanup; - strflags = virBitmapString(qemuCaps->flags); + strflags = virBitmapString(qemuCaps->flags, true); VIR_DEBUG("Version %u.%u.%u, cooked version %u, flags %s", major, minor, micro, *version, NULLSTR(strflags)); VIR_FREE(strflags); @@ -2376,7 +2376,7 @@ virQEMUCapsClear(virQEMUCapsPtr qemuCaps, char *virQEMUCapsFlagsString(virQEMUCapsPtr qemuCaps) { - return virBitmapString(qemuCaps->flags); + return virBitmapString(qemuCaps->flags, true); } diff --git a/src/util/virbitmap.c b/src/util/virbitmap.c index 57a8fbf82c78..cb6600074781 100644 --- a/src/util/virbitmap.c +++ b/src/util/virbitmap.c @@ -312,17 +312,19 @@ int virBitmapGetBit(virBitmapPtr bitmap, size_t b, bool *result) /** * virBitmapString: * @bitmap: Pointer to bitmap + * @prefix: Whether to prepend "0x" * * Convert @bitmap to printable string. * * Returns pointer to the string or NULL on error. */ -char *virBitmapString(virBitmapPtr bitmap) +char *virBitmapString(virBitmapPtr bitmap, bool prefix) { virBuffer buf = VIR_BUFFER_INITIALIZER; size_t sz; - virBufferAddLit(&buf, "0x"); + if (prefix) + virBufferAddLit(&buf, "0x"); sz = bitmap->map_len; diff --git a/src/util/virbitmap.h b/src/util/virbitmap.h index ffa3c42d792b..dc8fb71a07b8 100644 --- a/src/util/virbitmap.h +++ b/src/util/virbitmap.h @@ -80,7 +80,7 @@ bool virBitmapIsBitSet(virBitmapPtr bitmap, size_t b) int virBitmapGetBit(virBitmapPtr bitmap, size_t b, bool *result) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3) ATTRIBUTE_RETURN_CHECK; -char *virBitmapString(virBitmapPtr bitmap) +char *virBitmapString(virBitmapPtr bitmap, bool prefix) ATTRIBUTE_NONNULL(1) ATTRIBUTE_RETURN_CHECK; char *virBitmapFormat(virBitmapPtr bitmap); -- 2.15.0

$subj: s/virBitampString/virBitmapString/ On 11/13/2017 03:50 AM, Martin Kletzander wrote:
Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- src/qemu/qemu_capabilities.c | 4 ++-- src/util/virbitmap.c | 6 ++++-- src/util/virbitmap.h | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-)
FWIW: For some reason 4/21 hasn't yet made it to the mail server near me. I don't know why, it's just lost or on vacation. The only comment I have on it is that the commit message "util: Rename virBitmapString tovirBitmapToString" needs a slight adjustment "...String to virBit..." I have one adjustment below regarding one arg per line in function... Consider this a: Reviewed-by: John Ferlan <jferlan@redhat.com> For this and 04/21 because I'm way to lazy to figure out how to generate a reply to something I don't have the original email... John
diff --git a/src/qemu/qemu_capabilities.c b/src/qemu/qemu_capabilities.c index 7cb091056b48..c87feefb3be3 100644 --- a/src/qemu/qemu_capabilities.c +++ b/src/qemu/qemu_capabilities.c @@ -1509,7 +1509,7 @@ int virQEMUCapsParseHelpStr(const char *qemu, qemuCaps, check_yajl) < 0) goto cleanup;
- strflags = virBitmapString(qemuCaps->flags); + strflags = virBitmapString(qemuCaps->flags, true); VIR_DEBUG("Version %u.%u.%u, cooked version %u, flags %s", major, minor, micro, *version, NULLSTR(strflags)); VIR_FREE(strflags); @@ -2376,7 +2376,7 @@ virQEMUCapsClear(virQEMUCapsPtr qemuCaps,
char *virQEMUCapsFlagsString(virQEMUCapsPtr qemuCaps) { - return virBitmapString(qemuCaps->flags); + return virBitmapString(qemuCaps->flags, true); }
diff --git a/src/util/virbitmap.c b/src/util/virbitmap.c index 57a8fbf82c78..cb6600074781 100644 --- a/src/util/virbitmap.c +++ b/src/util/virbitmap.c @@ -312,17 +312,19 @@ int virBitmapGetBit(virBitmapPtr bitmap, size_t b, bool *result) /** * virBitmapString: * @bitmap: Pointer to bitmap + * @prefix: Whether to prepend "0x"
Or "prehex" ;-)
* * Convert @bitmap to printable string. * * Returns pointer to the string or NULL on error. */ -char *virBitmapString(virBitmapPtr bitmap) +char *virBitmapString(virBitmapPtr bitmap, bool prefix)
One line for each argument is the more "recent" style...
{ virBuffer buf = VIR_BUFFER_INITIALIZER; size_t sz;
- virBufferAddLit(&buf, "0x"); + if (prefix) + virBufferAddLit(&buf, "0x");
sz = bitmap->map_len;
diff --git a/src/util/virbitmap.h b/src/util/virbitmap.h index ffa3c42d792b..dc8fb71a07b8 100644 --- a/src/util/virbitmap.h +++ b/src/util/virbitmap.h @@ -80,7 +80,7 @@ bool virBitmapIsBitSet(virBitmapPtr bitmap, size_t b) int virBitmapGetBit(virBitmapPtr bitmap, size_t b, bool *result) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3) ATTRIBUTE_RETURN_CHECK;
-char *virBitmapString(virBitmapPtr bitmap) +char *virBitmapString(virBitmapPtr bitmap, bool prefix) ATTRIBUTE_NONNULL(1) ATTRIBUTE_RETURN_CHECK;
char *virBitmapFormat(virBitmapPtr bitmap);

On Mon, Nov 13, 2017 at 02:22:20PM -0500, John Ferlan wrote:
$subj:
s/virBitampString/virBitmapString/
hehe, good catch
On 11/13/2017 03:50 AM, Martin Kletzander wrote:
Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- src/qemu/qemu_capabilities.c | 4 ++-- src/util/virbitmap.c | 6 ++++-- src/util/virbitmap.h | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-)
FWIW: For some reason 4/21 hasn't yet made it to the mail server near me. I don't know why, it's just lost or on vacation. The only comment I have on it is that the commit message "util: Rename virBitmapString tovirBitmapToString" needs a slight adjustment "...String to virBit..."
You mean add a space between "to" and virBitmapToString? It is there.
I have one adjustment below regarding one arg per line in function... Consider this a:
Reviewed-by: John Ferlan <jferlan@redhat.com>
For this and 04/21 because I'm way to lazy to figure out how to generate a reply to something I don't have the original email...
Sure, mentioning here is enough.
John
diff --git a/src/qemu/qemu_capabilities.c b/src/qemu/qemu_capabilities.c index 7cb091056b48..c87feefb3be3 100644 --- a/src/qemu/qemu_capabilities.c +++ b/src/qemu/qemu_capabilities.c @@ -1509,7 +1509,7 @@ int virQEMUCapsParseHelpStr(const char *qemu, qemuCaps, check_yajl) < 0) goto cleanup;
- strflags = virBitmapString(qemuCaps->flags); + strflags = virBitmapString(qemuCaps->flags, true); VIR_DEBUG("Version %u.%u.%u, cooked version %u, flags %s", major, minor, micro, *version, NULLSTR(strflags)); VIR_FREE(strflags); @@ -2376,7 +2376,7 @@ virQEMUCapsClear(virQEMUCapsPtr qemuCaps,
char *virQEMUCapsFlagsString(virQEMUCapsPtr qemuCaps) { - return virBitmapString(qemuCaps->flags); + return virBitmapString(qemuCaps->flags, true); }
diff --git a/src/util/virbitmap.c b/src/util/virbitmap.c index 57a8fbf82c78..cb6600074781 100644 --- a/src/util/virbitmap.c +++ b/src/util/virbitmap.c @@ -312,17 +312,19 @@ int virBitmapGetBit(virBitmapPtr bitmap, size_t b, bool *result) /** * virBitmapString: * @bitmap: Pointer to bitmap + * @prefix: Whether to prepend "0x"
Or "prehex" ;-)
* * Convert @bitmap to printable string. * * Returns pointer to the string or NULL on error. */ -char *virBitmapString(virBitmapPtr bitmap) +char *virBitmapString(virBitmapPtr bitmap, bool prefix)
One line for each argument is the more "recent" style...
Sure, but a) mainly because otherwise it's a long line and b) it's along the other functions that do the same so the reading style is the same, but here it would just stand out as the only function, I think. Anyway, I fixed that as well.
{ virBuffer buf = VIR_BUFFER_INITIALIZER; size_t sz;
- virBufferAddLit(&buf, "0x"); + if (prefix) + virBufferAddLit(&buf, "0x");
sz = bitmap->map_len;
diff --git a/src/util/virbitmap.h b/src/util/virbitmap.h index ffa3c42d792b..dc8fb71a07b8 100644 --- a/src/util/virbitmap.h +++ b/src/util/virbitmap.h @@ -80,7 +80,7 @@ bool virBitmapIsBitSet(virBitmapPtr bitmap, size_t b) int virBitmapGetBit(virBitmapPtr bitmap, size_t b, bool *result) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3) ATTRIBUTE_RETURN_CHECK;
-char *virBitmapString(virBitmapPtr bitmap) +char *virBitmapString(virBitmapPtr bitmap, bool prefix) ATTRIBUTE_NONNULL(1) ATTRIBUTE_RETURN_CHECK;
char *virBitmapFormat(virBitmapPtr bitmap);

This follows the virBitmapToData() function and, similarly to virBitmapNewData(), we'll be able to have virBitmapNewString() later on without name confusion. Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- src/libvirt_private.syms | 2 +- src/qemu/qemu_capabilities.c | 4 ++-- src/util/virbitmap.c | 4 ++-- src/util/virbitmap.h | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index d4bae6150bb8..c11dc9ab5672 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1369,9 +1369,9 @@ virBitmapSetAll; virBitmapSetBit; virBitmapSetBitExpand; virBitmapSize; -virBitmapString; virBitmapToData; virBitmapToDataBuf; +virBitmapToString; # util/virbuffer.h diff --git a/src/qemu/qemu_capabilities.c b/src/qemu/qemu_capabilities.c index c87feefb3be3..60ca8ee9ef84 100644 --- a/src/qemu/qemu_capabilities.c +++ b/src/qemu/qemu_capabilities.c @@ -1509,7 +1509,7 @@ int virQEMUCapsParseHelpStr(const char *qemu, qemuCaps, check_yajl) < 0) goto cleanup; - strflags = virBitmapString(qemuCaps->flags, true); + strflags = virBitmapToString(qemuCaps->flags, true); VIR_DEBUG("Version %u.%u.%u, cooked version %u, flags %s", major, minor, micro, *version, NULLSTR(strflags)); VIR_FREE(strflags); @@ -2376,7 +2376,7 @@ virQEMUCapsClear(virQEMUCapsPtr qemuCaps, char *virQEMUCapsFlagsString(virQEMUCapsPtr qemuCaps) { - return virBitmapString(qemuCaps->flags, true); + return virBitmapToString(qemuCaps->flags, true); } diff --git a/src/util/virbitmap.c b/src/util/virbitmap.c index cb6600074781..58ef1a2e299b 100644 --- a/src/util/virbitmap.c +++ b/src/util/virbitmap.c @@ -310,7 +310,7 @@ int virBitmapGetBit(virBitmapPtr bitmap, size_t b, bool *result) } /** - * virBitmapString: + * virBitmapToString: * @bitmap: Pointer to bitmap * @prefix: Whether to prepend "0x" * @@ -318,7 +318,7 @@ int virBitmapGetBit(virBitmapPtr bitmap, size_t b, bool *result) * * Returns pointer to the string or NULL on error. */ -char *virBitmapString(virBitmapPtr bitmap, bool prefix) +char *virBitmapToString(virBitmapPtr bitmap, bool prefix) { virBuffer buf = VIR_BUFFER_INITIALIZER; size_t sz; diff --git a/src/util/virbitmap.h b/src/util/virbitmap.h index dc8fb71a07b8..99eb779d7188 100644 --- a/src/util/virbitmap.h +++ b/src/util/virbitmap.h @@ -80,7 +80,7 @@ bool virBitmapIsBitSet(virBitmapPtr bitmap, size_t b) int virBitmapGetBit(virBitmapPtr bitmap, size_t b, bool *result) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3) ATTRIBUTE_RETURN_CHECK; -char *virBitmapString(virBitmapPtr bitmap, bool prefix) +char *virBitmapToString(virBitmapPtr bitmap, bool prefix) ATTRIBUTE_NONNULL(1) ATTRIBUTE_RETURN_CHECK; char *virBitmapFormat(virBitmapPtr bitmap); -- 2.15.0

It is literally only a wrapper around virBitmapNewData() and virBitmapFormat(), only the naming was wrong since it was introduced. And because we have virBitmap*String functions where the meaning of the 'String' is constant, this might confuse someone. Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- src/libvirt_private.syms | 2 +- src/util/virbitmap.c | 6 +++--- src/util/virbitmap.h | 4 ++-- tests/virbitmaptest.c | 4 ++-- tools/virsh-domain.c | 4 ++-- tools/virsh-host.c | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index c11dc9ab5672..e535167e28fc 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1344,7 +1344,7 @@ virBitmapClearBit; virBitmapClearBitExpand; virBitmapCopy; virBitmapCountBits; -virBitmapDataToString; +virBitmapDataFormat; virBitmapEqual; virBitmapFormat; virBitmapFree; diff --git a/src/util/virbitmap.c b/src/util/virbitmap.c index 58ef1a2e299b..3d160aa4d50a 100644 --- a/src/util/virbitmap.c +++ b/src/util/virbitmap.c @@ -1044,7 +1044,7 @@ virBitmapCountBits(virBitmapPtr bitmap) } /** - * virBitmapDataToString: + * virBitmapDataFormat: * @data: the data * @len: length of @data in bytes * @@ -1054,8 +1054,8 @@ virBitmapCountBits(virBitmapPtr bitmap) * Returns: a string representation of the data, or NULL on error */ char * -virBitmapDataToString(const void *data, - int len) +virBitmapDataFormat(const void *data, + int len) { virBitmapPtr map = NULL; char *ret = NULL; diff --git a/src/util/virbitmap.h b/src/util/virbitmap.h index 99eb779d7188..720b389cfe52 100644 --- a/src/util/virbitmap.h +++ b/src/util/virbitmap.h @@ -136,8 +136,8 @@ ssize_t virBitmapNextClearBit(virBitmapPtr bitmap, ssize_t pos) size_t virBitmapCountBits(virBitmapPtr bitmap) ATTRIBUTE_NONNULL(1); -char *virBitmapDataToString(const void *data, - int len) +char *virBitmapDataFormat(const void *data, + int len) ATTRIBUTE_NONNULL(1); bool virBitmapOverlaps(virBitmapPtr b1, virBitmapPtr b2) diff --git a/tests/virbitmaptest.c b/tests/virbitmaptest.c index c24d4a72f70a..488796719dd9 100644 --- a/tests/virbitmaptest.c +++ b/tests/virbitmaptest.c @@ -336,12 +336,12 @@ test5(const void *v ATTRIBUTE_UNUSED) data2[4] != 0x04) goto error; - if (!(str = virBitmapDataToString(data, sizeof(data)))) + if (!(str = virBitmapDataFormat(data, sizeof(data)))) goto error; if (STRNEQ(str, "0,9,34")) goto error; VIR_FREE(str); - if (!(str = virBitmapDataToString(data2, len2))) + if (!(str = virBitmapDataFormat(data2, len2))) goto error; if (STRNEQ(str, "0,2,9,15,34")) goto error; diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index 42d5526374fc..adfde692993a 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -6527,7 +6527,7 @@ virshVcpuinfoPrintAffinity(vshControl *ctl, vshPrint(ctl, "%-15s ", _("CPU Affinity:")); if (pretty) { - if (!(str = virBitmapDataToString(cpumap, VIR_CPU_MAPLEN(maxcpu)))) + if (!(str = virBitmapDataFormat(cpumap, VIR_CPU_MAPLEN(maxcpu)))) goto cleanup; vshPrint(ctl, _("%s (out of %d)"), str, maxcpu); } else { @@ -6781,7 +6781,7 @@ virshPrintPinInfo(vshControl *ctl, { char *str = NULL; - if (!(str = virBitmapDataToString(cpumap, cpumaplen))) + if (!(str = virBitmapDataFormat(cpumap, cpumaplen))) return false; vshPrint(ctl, "%s", str); diff --git a/tools/virsh-host.c b/tools/virsh-host.c index 5509065fd606..ecaf830e350d 100644 --- a/tools/virsh-host.c +++ b/tools/virsh-host.c @@ -717,7 +717,7 @@ cmdNodeCpuMap(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED) vshPrint(ctl, "%-15s ", _("CPU map:")); if (pretty) { - char *str = virBitmapDataToString(cpumap, VIR_CPU_MAPLEN(cpunum)); + char *str = virBitmapDataFormat(cpumap, VIR_CPU_MAPLEN(cpunum)); if (!str) goto cleanup; -- 2.15.0

On 11/13/2017 03:50 AM, Martin Kletzander wrote:
It is literally only a wrapper around virBitmapNewData() and virBitmapFormat(), only the naming was wrong since it was introduced.
in commit id '7d8afc47'.
And because we have virBitmap*String functions where the meaning of the 'String' is constant, this might confuse someone.
And some would say DataFormatWhat - cannot please everyone though ;-) because "naming is hard" (per abologna). FWIW: When you noted the 'String' is constant, I immediately thought about a "const char *String" meaning it wouldn't need to be free'd.
Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- src/libvirt_private.syms | 2 +- src/util/virbitmap.c | 6 +++--- src/util/virbitmap.h | 4 ++-- tests/virbitmaptest.c | 4 ++-- tools/virsh-domain.c | 4 ++-- tools/virsh-host.c | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-)
So like patch 04/21 - patch 06/21 also hasn't shown up yet, so please consider the following for comments and R-b (all the others showed up). 1. The commit message needs massaging - "fromvirBitmapToString" to be "from virBitmapToString" 2. The 3rd argument should be on it's only line (like noted in 03) With the slight adjustment below, consider both 05 and 06 as: Reviewed-by: John Ferlan <jferlan@redhat.com> John [...]
diff --git a/tests/virbitmaptest.c b/tests/virbitmaptest.c index c24d4a72f70a..488796719dd9 100644 --- a/tests/virbitmaptest.c +++ b/tests/virbitmaptest.c
There's a comment above here that needs adjustment ... s/DataToString/DataFormat to be consistent...
@@ -336,12 +336,12 @@ test5(const void *v ATTRIBUTE_UNUSED) data2[4] != 0x04) goto error;
- if (!(str = virBitmapDataToString(data, sizeof(data)))) + if (!(str = virBitmapDataFormat(data, sizeof(data)))) goto error; if (STRNEQ(str, "0,9,34")) goto error; VIR_FREE(str); - if (!(str = virBitmapDataToString(data2, len2))) + if (!(str = virBitmapDataFormat(data2, len2))) goto error; if (STRNEQ(str, "0,2,9,15,34")) goto error; [...]

Truncate the output so that it is only as big as is needed to fit all the bits, not all the units from the map. This will be needed in the future in order to properly format bitmaps for kernel's sysfs files. Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- src/qemu/qemu_capabilities.c | 4 ++-- src/util/virbitmap.c | 29 +++++++++++++++++++++++++++-- src/util/virbitmap.h | 2 +- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/qemu/qemu_capabilities.c b/src/qemu/qemu_capabilities.c index 60ca8ee9ef84..232f88af0fb8 100644 --- a/src/qemu/qemu_capabilities.c +++ b/src/qemu/qemu_capabilities.c @@ -1509,7 +1509,7 @@ int virQEMUCapsParseHelpStr(const char *qemu, qemuCaps, check_yajl) < 0) goto cleanup; - strflags = virBitmapToString(qemuCaps->flags, true); + strflags = virBitmapToString(qemuCaps->flags, true, false); VIR_DEBUG("Version %u.%u.%u, cooked version %u, flags %s", major, minor, micro, *version, NULLSTR(strflags)); VIR_FREE(strflags); @@ -2376,7 +2376,7 @@ virQEMUCapsClear(virQEMUCapsPtr qemuCaps, char *virQEMUCapsFlagsString(virQEMUCapsPtr qemuCaps) { - return virBitmapToString(qemuCaps->flags, true); + return virBitmapToString(qemuCaps->flags, true, false); } diff --git a/src/util/virbitmap.c b/src/util/virbitmap.c index 3d160aa4d50a..0a7d3452b90c 100644 --- a/src/util/virbitmap.c +++ b/src/util/virbitmap.c @@ -313,15 +313,19 @@ int virBitmapGetBit(virBitmapPtr bitmap, size_t b, bool *result) * virBitmapToString: * @bitmap: Pointer to bitmap * @prefix: Whether to prepend "0x" + * @trim: Whether to output only the minimum required characters * * Convert @bitmap to printable string. * * Returns pointer to the string or NULL on error. */ -char *virBitmapToString(virBitmapPtr bitmap, bool prefix) +char *virBitmapToString(virBitmapPtr bitmap, bool prefix, bool trim) { virBuffer buf = VIR_BUFFER_INITIALIZER; size_t sz; + size_t len; + size_t diff; + char *ret = NULL; if (prefix) virBufferAddLit(&buf, "0x"); @@ -335,7 +339,28 @@ char *virBitmapToString(virBitmapPtr bitmap, bool prefix) } virBufferCheckError(&buf); - return virBufferContentAndReset(&buf); + ret = virBufferContentAndReset(&buf); + if (!ret) + return NULL; + + if (!trim) + return ret; + + if (bitmap->max_bit != bitmap->map_len * VIR_BITMAP_BITS_PER_UNIT) { + char *tmp = ret; + + if (prefix) + tmp += 2; + + len = strlen(tmp); + sz = VIR_DIV_UP(bitmap->max_bit, 4); + diff = len - sz; + + if (diff) + memmove(tmp, tmp + diff, sz + 1); + } + + return ret; } /** diff --git a/src/util/virbitmap.h b/src/util/virbitmap.h index 720b389cfe52..02acb7519d37 100644 --- a/src/util/virbitmap.h +++ b/src/util/virbitmap.h @@ -80,7 +80,7 @@ bool virBitmapIsBitSet(virBitmapPtr bitmap, size_t b) int virBitmapGetBit(virBitmapPtr bitmap, size_t b, bool *result) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3) ATTRIBUTE_RETURN_CHECK; -char *virBitmapToString(virBitmapPtr bitmap, bool prefix) +char *virBitmapToString(virBitmapPtr bitmap, bool prefix, bool trim) ATTRIBUTE_NONNULL(1) ATTRIBUTE_RETURN_CHECK; char *virBitmapFormat(virBitmapPtr bitmap); -- 2.15.0

Our bitmaps can be represented as data (raw bytes for which we have virBitmapNewData() and virBitmapToData()), human representation (list of numbers in a string for which we have virBitmapParse() and virBitmapFormat()) and hexadecimal string (for which we have only virBitmapToString()). So let's add the missing complement for the last one so that we can parse hexadecimal strings. Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- src/libvirt_private.syms | 1 + src/util/virbitmap.c | 36 ++++++++++++++++++++++++++++++++++++ src/util/virbitmap.h | 4 ++++ tests/virbitmaptest.c | 38 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index e535167e28fc..57df411602b9 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1359,6 +1359,7 @@ virBitmapNewCopy; virBitmapNewData; virBitmapNewEmpty; virBitmapNewQuiet; +virBitmapNewString; virBitmapNextClearBit; virBitmapNextSetBit; virBitmapOverlaps; diff --git a/src/util/virbitmap.c b/src/util/virbitmap.c index 0a7d3452b90c..02d1f264d859 100644 --- a/src/util/virbitmap.c +++ b/src/util/virbitmap.c @@ -36,6 +36,7 @@ #include "c-ctype.h" #include "count-one-bits.h" #include "virstring.h" +#include "virutil.h" #include "virerror.h" #define VIR_FROM_THIS VIR_FROM_NONE @@ -1068,6 +1069,41 @@ virBitmapCountBits(virBitmapPtr bitmap) return ret; } +/** + * virBitmapNewString: + * @string: the string + * + * Allocate a bitmap from a string of hexadecimal data. + * + * Returns a pointer to the allocated bitmap or NULL if + * memory cannot be allocated. + */ +virBitmapPtr +virBitmapNewString(const char *string) +{ + virBitmapPtr bitmap; + size_t i = 0; + size_t len = strlen(string); + + if (strspn(string, "0123456789abcdefABCDEF") != len) { + virReportError(VIR_ERR_INVALID_ARG, + _("Invalid hexadecimal string '%s'"), string); + return NULL; + } + + bitmap = virBitmapNew(len * 4); + if (!bitmap) + return NULL; + + for (i = 0; i < len; i++) { + unsigned long nibble = virHexToBin(string[len - i - 1]); + nibble <<= VIR_BITMAP_BIT_OFFSET(i * 4); + bitmap->map[VIR_BITMAP_UNIT_OFFSET(i * 4)] |= nibble; + } + + return bitmap; +} + /** * virBitmapDataFormat: * @data: the data diff --git a/src/util/virbitmap.h b/src/util/virbitmap.h index 02acb7519d37..e964a3edc9cb 100644 --- a/src/util/virbitmap.h +++ b/src/util/virbitmap.h @@ -80,6 +80,10 @@ bool virBitmapIsBitSet(virBitmapPtr bitmap, size_t b) int virBitmapGetBit(virBitmapPtr bitmap, size_t b, bool *result) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3) ATTRIBUTE_RETURN_CHECK; +virBitmapPtr +virBitmapNewString(const char *string) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_RETURN_CHECK; + char *virBitmapToString(virBitmapPtr bitmap, bool prefix, bool trim) ATTRIBUTE_NONNULL(1) ATTRIBUTE_RETURN_CHECK; diff --git a/tests/virbitmaptest.c b/tests/virbitmaptest.c index 488796719dd9..3ea63e1295af 100644 --- a/tests/virbitmaptest.c +++ b/tests/virbitmaptest.c @@ -663,6 +663,42 @@ test12(const void *opaque ATTRIBUTE_UNUSED) return ret; } + +/* virBitmap(New/To)String */ +static int +test13(const void *opaque ATTRIBUTE_UNUSED) +{ + virBitmapPtr map = NULL; + const char *strings[] = { "1234feebee", "000c0fefe" }; + char *str = NULL; + size_t i = 0; + int ret = -1; + + for (i = 0; i < ARRAY_CARDINALITY(strings); i++) { + map = virBitmapNewString(strings[i]); + str = virBitmapToString(map, false, true); + + if (!map || !str) + goto cleanup; + + if (STRNEQ(strings[i], str)) { + fprintf(stderr, "\n expected bitmap string '%s' actual string " + "'%s'\n", NULLSTR(strings[i]), NULLSTR(str)); + goto cleanup; + } + + VIR_FREE(str); + virBitmapFree(map); + map = NULL; + } + + ret = 0; + cleanup: + VIR_FREE(str); + virBitmapFree(map); + return ret; +} + #undef TEST_MAP @@ -711,6 +747,8 @@ mymain(void) if (virTestRun("test12", test12, NULL) < 0) ret = -1; + if (virTestRun("test13", test13, NULL) < 0) + ret = -1; return ret; } -- 2.15.0

On 11/13/2017 03:50 AM, Martin Kletzander wrote:
Our bitmaps can be represented as data (raw bytes for which we have virBitmapNewData() and virBitmapToData()), human representation (list of numbers in a string for which we have virBitmapParse() and virBitmapFormat()) and hexadecimal string (for which we have only virBitmapToString()). So let's add the missing complement for the last one so that we can parse hexadecimal strings.
Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- src/libvirt_private.syms | 1 + src/util/virbitmap.c | 36 ++++++++++++++++++++++++++++++++++++ src/util/virbitmap.h | 4 ++++ tests/virbitmaptest.c | 38 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+)
With a couple of minor/noted adjustments below, Reviewed-by: John Ferlan <jferlan@redhat.com> John
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index e535167e28fc..57df411602b9 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1359,6 +1359,7 @@ virBitmapNewCopy; virBitmapNewData; virBitmapNewEmpty; virBitmapNewQuiet; +virBitmapNewString; virBitmapNextClearBit; virBitmapNextSetBit; virBitmapOverlaps; diff --git a/src/util/virbitmap.c b/src/util/virbitmap.c index 0a7d3452b90c..02d1f264d859 100644 --- a/src/util/virbitmap.c +++ b/src/util/virbitmap.c @@ -36,6 +36,7 @@ #include "c-ctype.h" #include "count-one-bits.h" #include "virstring.h" +#include "virutil.h" #include "virerror.h"
#define VIR_FROM_THIS VIR_FROM_NONE @@ -1068,6 +1069,41 @@ virBitmapCountBits(virBitmapPtr bitmap) return ret; }
Two blank lines for new functions
+/** + * virBitmapNewString: + * @string: the string
to be converted into a bitmap
+ * + * Allocate a bitmap from a string of hexadecimal data. + * + * Returns a pointer to the allocated bitmap or NULL if + * memory cannot be allocated. + */ +virBitmapPtr +virBitmapNewString(const char *string) +{ + virBitmapPtr bitmap; + size_t i = 0; + size_t len = strlen(string); + + if (strspn(string, "0123456789abcdefABCDEF") != len) { + virReportError(VIR_ERR_INVALID_ARG, + _("Invalid hexadecimal string '%s'"), string); + return NULL; + } + + bitmap = virBitmapNew(len * 4); + if (!bitmap) + return NULL; + + for (i = 0; i < len; i++) { + unsigned long nibble = virHexToBin(string[len - i - 1]); + nibble <<= VIR_BITMAP_BIT_OFFSET(i * 4); + bitmap->map[VIR_BITMAP_UNIT_OFFSET(i * 4)] |= nibble; + } + + return bitmap; +} +
Two blank lines
/** * virBitmapDataFormat: * @data: the data diff --git a/src/util/virbitmap.h b/src/util/virbitmap.h index 02acb7519d37..e964a3edc9cb 100644 --- a/src/util/virbitmap.h +++ b/src/util/virbitmap.h @@ -80,6 +80,10 @@ bool virBitmapIsBitSet(virBitmapPtr bitmap, size_t b) int virBitmapGetBit(virBitmapPtr bitmap, size_t b, bool *result) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3) ATTRIBUTE_RETURN_CHECK;
+virBitmapPtr +virBitmapNewString(const char *string) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_RETURN_CHECK; + char *virBitmapToString(virBitmapPtr bitmap, bool prefix, bool trim) ATTRIBUTE_NONNULL(1) ATTRIBUTE_RETURN_CHECK;
diff --git a/tests/virbitmaptest.c b/tests/virbitmaptest.c index 488796719dd9..3ea63e1295af 100644 --- a/tests/virbitmaptest.c +++ b/tests/virbitmaptest.c @@ -663,6 +663,42 @@ test12(const void *opaque ATTRIBUTE_UNUSED) return ret; }
+ +/* virBitmap(New/To)String */ +static int +test13(const void *opaque ATTRIBUTE_UNUSED) +{ + virBitmapPtr map = NULL; + const char *strings[] = { "1234feebee", "000c0fefe" };
hahahaha, but you're missing the 'v' (covfefe)
+ char *str = NULL; + size_t i = 0; + int ret = -1; + + for (i = 0; i < ARRAY_CARDINALITY(strings); i++) { + map = virBitmapNewString(strings[i]); + str = virBitmapToString(map, false, true); + + if (!map || !str) + goto cleanup; + + if (STRNEQ(strings[i], str)) { + fprintf(stderr, "\n expected bitmap string '%s' actual string " + "'%s'\n", NULLSTR(strings[i]), NULLSTR(str));
Don't believe either needs NULLSTR.... If they did STRNEQ would probably need NULLABLE too ;-)... Still @str cannot be NULL if we get here and if strings[i] is NULL, then something very strange happened.
+ goto cleanup; + } + + VIR_FREE(str); + virBitmapFree(map); + map = NULL; + } + + ret = 0; + cleanup: + VIR_FREE(str); + virBitmapFree(map); + return ret; +} + #undef TEST_MAP
@@ -711,6 +747,8 @@ mymain(void)
if (virTestRun("test12", test12, NULL) < 0) ret = -1; + if (virTestRun("test13", test13, NULL) < 0) + ret = -1;
return ret; }

Already introduced in the past with 9479642fd3c5, but then renamed to virBitmapIntersect by a908e9e45eb2. This time we'll really use it. Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- src/libvirt_private.syms | 1 + src/util/virbitmap.c | 22 ++++++++++++++++++++++ src/util/virbitmap.h | 3 +++ tests/virbitmaptest.c | 42 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 57df411602b9..0b78a0681c5e 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1370,6 +1370,7 @@ virBitmapSetAll; virBitmapSetBit; virBitmapSetBitExpand; virBitmapSize; +virBitmapSubtract; virBitmapToData; virBitmapToDataBuf; virBitmapToString; diff --git a/src/util/virbitmap.c b/src/util/virbitmap.c index 02d1f264d859..ac6ff4f6d26d 100644 --- a/src/util/virbitmap.c +++ b/src/util/virbitmap.c @@ -1169,3 +1169,25 @@ virBitmapIntersect(virBitmapPtr a, for (i = 0; i < max; i++) a->map[i] &= b->map[i]; } + + +/** + * virBitmapSubtract: + * @a: minuend/result + * @b: subtrahend + * + * Performs subtraction of two bitmaps: a = a - b + */ +void +virBitmapSubtract(virBitmapPtr a, + virBitmapPtr b) +{ + size_t i; + size_t max = a->map_len; + + if (max > b->map_len) + max = b->map_len; + + for (i = 0; i < max; i++) + a->map[i] &= ~b->map[i]; +} diff --git a/src/util/virbitmap.h b/src/util/virbitmap.h index e964a3edc9cb..7b2bea8b534c 100644 --- a/src/util/virbitmap.h +++ b/src/util/virbitmap.h @@ -150,4 +150,7 @@ bool virBitmapOverlaps(virBitmapPtr b1, void virBitmapIntersect(virBitmapPtr a, virBitmapPtr b) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); +void virBitmapSubtract(virBitmapPtr a, virBitmapPtr b) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); + #endif diff --git a/tests/virbitmaptest.c b/tests/virbitmaptest.c index 3ea63e1295af..120c074d3def 100644 --- a/tests/virbitmaptest.c +++ b/tests/virbitmaptest.c @@ -701,6 +701,39 @@ test13(const void *opaque ATTRIBUTE_UNUSED) #undef TEST_MAP +static int +test14(const void *opaque) +{ + const struct testBinaryOpData *data = opaque; + virBitmapPtr amap = NULL; + virBitmapPtr bmap = NULL; + virBitmapPtr resmap = NULL; + int ret = -1; + + if (virBitmapParse(data->a, &amap, 256) < 0 || + virBitmapParse(data->b, &bmap, 256) < 0 || + virBitmapParse(data->res, &resmap, 256) < 0) + goto cleanup; + + virBitmapSubtract(amap, bmap); + + if (!virBitmapEqual(amap, resmap)) { + fprintf(stderr, + "\n bitmap subtraction failed: '%s' - '%s' != '%s'\n", + data->a, data->b, data->res); + goto cleanup; + } + + ret = 0; + + cleanup: + virBitmapFree(amap); + virBitmapFree(bmap); + virBitmapFree(resmap); + + return ret; +} + #define TESTBINARYOP(A, B, RES, FUNC) \ testBinaryOpData.a = A; \ @@ -750,6 +783,15 @@ mymain(void) if (virTestRun("test13", test13, NULL) < 0) ret = -1; + virTestCounterReset("test14-"); + TESTBINARYOP("0", "0", "0,^0", test14); + TESTBINARYOP("0-3", "0", "1-3", test14); + TESTBINARYOP("0-3", "0,3", "1-2", test14); + TESTBINARYOP("0,^0", "0", "0,^0", test14); + TESTBINARYOP("0-3", "0-3", "0,^0", test14); + TESTBINARYOP("0-3", "0,^0", "0-3", test14); + TESTBINARYOP("0,2", "1,3", "0,2", test14); + return ret; } -- 2.15.0

On 11/13/2017 03:50 AM, Martin Kletzander wrote:
Already introduced in the past with 9479642fd3c5, but then renamed to virBitmapIntersect by a908e9e45eb2. This time we'll really use it.
Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- src/libvirt_private.syms | 1 + src/util/virbitmap.c | 22 ++++++++++++++++++++++ src/util/virbitmap.h | 3 +++ tests/virbitmaptest.c | 42 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+)
Reviewed-by: John Ferlan <jferlan@redhat.com> John

Sometimes the size of the bitmap matters and it might not be guessed correctly when parsing from some type of input. For example virBitmapNewData() has Byte granularity, virBitmapNewString() has nibble granularity and so on. virBitmapParseUnlimited() can be tricked into creating huge bitmap that's not needed (e.g.: "0-2,^99999999"). This function provides a way to shrink the bitmap. It is not supposed to free any memory. Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- src/libvirt_private.syms | 1 + src/util/virbitmap.c | 19 +++++++++++++++++++ src/util/virbitmap.h | 2 ++ 3 files changed, 22 insertions(+) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 0b78a0681c5e..3986cc523e39 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1369,6 +1369,7 @@ virBitmapParseUnlimited; virBitmapSetAll; virBitmapSetBit; virBitmapSetBitExpand; +virBitmapShrink; virBitmapSize; virBitmapSubtract; virBitmapToData; diff --git a/src/util/virbitmap.c b/src/util/virbitmap.c index ac6ff4f6d26d..95b1f8656907 100644 --- a/src/util/virbitmap.c +++ b/src/util/virbitmap.c @@ -176,6 +176,25 @@ int virBitmapSetBit(virBitmapPtr bitmap, size_t b) return 0; } + +/** + * virBitmapShrink: + * @map: Pointer to bitmap + * @b: last bit position to be excluded from bitmap + * + * Resizes the bitmap so that no more than @b bits will fit into it. Nothing + * will change if the size is already smaller than @b. + */ +void virBitmapShrink(virBitmapPtr map, size_t b) +{ + if (!map) + return; + + if (map->max_bit >= b) + map->max_bit = b; +} + + /** * virBitmapExpand: * @map: Pointer to bitmap diff --git a/src/util/virbitmap.h b/src/util/virbitmap.h index 7b2bea8b534c..2464814055de 100644 --- a/src/util/virbitmap.h +++ b/src/util/virbitmap.h @@ -153,4 +153,6 @@ void virBitmapIntersect(virBitmapPtr a, virBitmapPtr b) void virBitmapSubtract(virBitmapPtr a, virBitmapPtr b) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); +void virBitmapShrink(virBitmapPtr map, size_t b); + #endif -- 2.15.0

On 11/13/2017 03:50 AM, Martin Kletzander wrote:
Sometimes the size of the bitmap matters and it might not be guessed correctly when parsing from some type of input. For example virBitmapNewData() has Byte granularity, virBitmapNewString() has nibble granularity and so on. virBitmapParseUnlimited() can be tricked into creating huge bitmap that's not needed (e.g.: "0-2,^99999999"). This function provides a way to shrink the bitmap. It is not supposed to free any memory.
Is there a specific reason why you don't free memory? Consider that the corollary virBitmapExpand can always be used to regrow the bitmap. I'm fine with not free'ing, but maybe someone would want to... OK, sure they can supply the patch some day, I know...
Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- src/libvirt_private.syms | 1 + src/util/virbitmap.c | 19 +++++++++++++++++++ src/util/virbitmap.h | 2 ++ 3 files changed, 22 insertions(+)
With a couple of notes below handled, Reviewed-by: John Ferlan <jferlan@redhat.com> John
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 0b78a0681c5e..3986cc523e39 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1369,6 +1369,7 @@ virBitmapParseUnlimited; virBitmapSetAll; virBitmapSetBit; virBitmapSetBitExpand; +virBitmapShrink; virBitmapSize; virBitmapSubtract; virBitmapToData; diff --git a/src/util/virbitmap.c b/src/util/virbitmap.c index ac6ff4f6d26d..95b1f8656907 100644 --- a/src/util/virbitmap.c +++ b/src/util/virbitmap.c @@ -176,6 +176,25 @@ int virBitmapSetBit(virBitmapPtr bitmap, size_t b) return 0; }
+ +/** + * virBitmapShrink: + * @map: Pointer to bitmap + * @b: last bit position to be excluded from bitmap + * + * Resizes the bitmap so that no more than @b bits will fit into it. Nothing + * will change if the size is already smaller than @b.
Considering adding, "NB: Does not adjust the map->map_len so that a subsequent virBitmapExpand doesn't necessarily need to reallocate." (not required, just a suggestion)
+ */ +void virBitmapShrink(virBitmapPtr map, size_t b)
void virBitmapStrink(virBitmapPtr map, size_t b)
+{ + if (!map) + return; + + if (map->max_bit >= b) + map->max_bit = b; +} + + /** * virBitmapExpand: * @map: Pointer to bitmap diff --git a/src/util/virbitmap.h b/src/util/virbitmap.h index 7b2bea8b534c..2464814055de 100644 --- a/src/util/virbitmap.h +++ b/src/util/virbitmap.h @@ -153,4 +153,6 @@ void virBitmapIntersect(virBitmapPtr a, virBitmapPtr b) void virBitmapSubtract(virBitmapPtr a, virBitmapPtr b) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);
+void virBitmapShrink(virBitmapPtr map, size_t b); +
Not that it matters but it's always nice to keep the .h file in the same relative order as the .c file... So this would move below virBitmapSetBit
#endif

On Mon, Nov 13, 2017 at 03:42:40PM -0500, John Ferlan wrote:
On 11/13/2017 03:50 AM, Martin Kletzander wrote:
Sometimes the size of the bitmap matters and it might not be guessed correctly when parsing from some type of input. For example virBitmapNewData() has Byte granularity, virBitmapNewString() has nibble granularity and so on. virBitmapParseUnlimited() can be tricked into creating huge bitmap that's not needed (e.g.: "0-2,^99999999"). This function provides a way to shrink the bitmap. It is not supposed to free any memory.
Is there a specific reason why you don't free memory? Consider that the corollary virBitmapExpand can always be used to regrow the bitmap. I'm fine with not free'ing, but maybe someone would want to... OK, sure they can supply the patch some day, I know...
I don't free it because a) it would cost more time and b) we over-allocate a bit anyway. Also this is mainly used so that the bitmap size is predictable, not much else.
Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- src/libvirt_private.syms | 1 + src/util/virbitmap.c | 19 +++++++++++++++++++ src/util/virbitmap.h | 2 ++ 3 files changed, 22 insertions(+)
With a couple of notes below handled,
Reviewed-by: John Ferlan <jferlan@redhat.com>
John
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 0b78a0681c5e..3986cc523e39 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1369,6 +1369,7 @@ virBitmapParseUnlimited; virBitmapSetAll; virBitmapSetBit; virBitmapSetBitExpand; +virBitmapShrink; virBitmapSize; virBitmapSubtract; virBitmapToData; diff --git a/src/util/virbitmap.c b/src/util/virbitmap.c index ac6ff4f6d26d..95b1f8656907 100644 --- a/src/util/virbitmap.c +++ b/src/util/virbitmap.c @@ -176,6 +176,25 @@ int virBitmapSetBit(virBitmapPtr bitmap, size_t b) return 0; }
+ +/** + * virBitmapShrink: + * @map: Pointer to bitmap + * @b: last bit position to be excluded from bitmap + * + * Resizes the bitmap so that no more than @b bits will fit into it. Nothing + * will change if the size is already smaller than @b.
Considering adding, "NB: Does not adjust the map->map_len so that a subsequent virBitmapExpand doesn't necessarily need to reallocate." (not required, just a suggestion)
Added
+ */ +void virBitmapShrink(virBitmapPtr map, size_t b)
void virBitmapStrink(virBitmapPtr map, size_t b)
done
+{ + if (!map) + return; + + if (map->max_bit >= b) + map->max_bit = b; +} + + /** * virBitmapExpand: * @map: Pointer to bitmap diff --git a/src/util/virbitmap.h b/src/util/virbitmap.h index 7b2bea8b534c..2464814055de 100644 --- a/src/util/virbitmap.h +++ b/src/util/virbitmap.h @@ -153,4 +153,6 @@ void virBitmapIntersect(virBitmapPtr a, virBitmapPtr b) void virBitmapSubtract(virBitmapPtr a, virBitmapPtr b) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);
+void virBitmapShrink(virBitmapPtr map, size_t b); +
Not that it matters but it's always nice to keep the .h file in the same relative order as the .c file... So this would move below virBitmapSetBit
I went the other way and moved it in the .c file, I have no idea why I didn't put it in the end anyway.
#endif

Because the cache banks are initialized based on the order in which their respective directories exist on the filesystem, they can appear in diferrent order. This is here mainly for tests because the cache directory might have different order of children nodes and tests would fail otherwise. It should not be the case with sysfs, but one can never be sure. And this does not take almost any extra time, mainly because it gets initialized once per driver. Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- src/conf/capabilities.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/conf/capabilities.c b/src/conf/capabilities.c index 9920a675aca3..095ef51c424a 100644 --- a/src/conf/capabilities.c +++ b/src/conf/capabilities.c @@ -1561,6 +1561,20 @@ virCapsHostCacheBankFree(virCapsHostCacheBankPtr ptr) VIR_FREE(ptr); } +static int +virCapsHostCacheBankSorter(const void *a, const void *b) +{ + virCapsHostCacheBankPtr ca = *(virCapsHostCacheBankPtr *)a; + virCapsHostCacheBankPtr cb = *(virCapsHostCacheBankPtr *)b; + + if (ca->level < cb->level) + return -1; + if (ca->level > cb->level) + return 1; + + return ca->id - cb->id; +} + int virCapabilitiesInitCaches(virCapsPtr caps) { @@ -1700,6 +1714,12 @@ virCapabilitiesInitCaches(virCapsPtr caps) goto cleanup; } + /* Sort the array in order for the tests to be predicable. This way we can + * still traverse the directory instead of guessing names (in case there is + * 'index1' and 'index3' but no 'index2'). */ + qsort(caps->host.caches, caps->host.ncaches, + sizeof(*caps->host.caches), virCapsHostCacheBankSorter); + ret = 0; cleanup: VIR_FREE(type); -- 2.15.0

On 11/13/2017 03:50 AM, Martin Kletzander wrote:
Because the cache banks are initialized based on the order in which their respective directories exist on the filesystem, they can appear in diferrent
s/diferrent/different/
order. This is here mainly for tests because the cache directory might have different order of children nodes and tests would fail otherwise. It should not be the case with sysfs, but one can never be sure. And this does not take almost any extra time, mainly because it gets initialized once per driver.
Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- src/conf/capabilities.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+)
A couple of fingers not typing what the brain told them to type and a couple of other nits, for Reviewed-by: John Ferlan <jferlan@redhat.com> John
diff --git a/src/conf/capabilities.c b/src/conf/capabilities.c index 9920a675aca3..095ef51c424a 100644 --- a/src/conf/capabilities.c +++ b/src/conf/capabilities.c @@ -1561,6 +1561,20 @@ virCapsHostCacheBankFree(virCapsHostCacheBankPtr ptr) VIR_FREE(ptr); }
Two blank lines new functions
+static int +virCapsHostCacheBankSorter(const void *a, const void *b)
One line per argument
+{ + virCapsHostCacheBankPtr ca = *(virCapsHostCacheBankPtr *)a; + virCapsHostCacheBankPtr cb = *(virCapsHostCacheBankPtr *)b; + + if (ca->level < cb->level) + return -1; + if (ca->level > cb->level) + return 1; + + return ca->id - cb->id; +} + int virCapabilitiesInitCaches(virCapsPtr caps) { @@ -1700,6 +1714,12 @@ virCapabilitiesInitCaches(virCapsPtr caps) goto cleanup; }
+ /* Sort the array in order for the tests to be predicable. This way we can
predictable (weird, my email browser didn't flag the other spelling)
+ * still traverse the directory instead of guessing names (in case there is + * 'index1' and 'index3' but no 'index2'). */ + qsort(caps->host.caches, caps->host.ncaches, + sizeof(*caps->host.caches), virCapsHostCacheBankSorter); + ret = 0; cleanup: VIR_FREE(type);

Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- src/conf/capabilities.c | 45 ++++++++++++++-------- tests/vircaps2xmldata/vircaps-x86_64-caches.xml | 2 +- .../vircaps2xmldata/vircaps-x86_64-resctrl-cdp.xml | 4 +- .../vircaps2xmldata/vircaps-x86_64-resctrl-skx.xml | 4 +- tests/vircaps2xmldata/vircaps-x86_64-resctrl.xml | 4 +- 5 files changed, 36 insertions(+), 23 deletions(-) diff --git a/src/conf/capabilities.c b/src/conf/capabilities.c index 095ef51c424a..5bf8ac2019f9 100644 --- a/src/conf/capabilities.c +++ b/src/conf/capabilities.c @@ -883,7 +883,8 @@ virCapabilitiesFormatCaches(virBufferPtr buf, for (i = 0; i < ncaches; i++) { virCapsHostCacheBankPtr bank = caches[i]; char *cpus_str = virBitmapFormat(bank->cpus); - bool kilos = !(bank->size % 1024); + const char *unit = NULL; + unsigned long long short_size = virPrettySize(bank->size, &unit); if (!cpus_str) return -1; @@ -897,32 +898,44 @@ virCapabilitiesFormatCaches(virBufferPtr buf, "size='%llu' unit='%s' cpus='%s'", bank->id, bank->level, virCacheTypeToString(bank->type), - bank->size >> (kilos * 10), - kilos ? "KiB" : "B", - cpus_str); + short_size, unit, cpus_str); VIR_FREE(cpus_str); virBufferSetChildIndent(&controlBuf, buf); for (j = 0; j < bank->ncontrols; j++) { - bool min_kilos = !(bank->controls[j]->granularity % 1024); - - /* Only use KiB if both values are divisible */ - if (bank->controls[j]->min) - min_kilos = min_kilos && !(bank->controls[j]->min % 1024); + const char *min_unit; + unsigned long long gran_short_size = bank->controls[j]->granularity; + unsigned long long min_short_size = bank->controls[j]->min; + + gran_short_size = virPrettySize(gran_short_size, &unit); + min_short_size = virPrettySize(min_short_size, &min_unit); + + /* Only use the smaller unit if they are different */ + if (min_short_size) { + unsigned long long gran_div; + unsigned long long min_div; + + gran_div = bank->controls[j]->granularity / gran_short_size; + min_div = bank->controls[j]->min / min_short_size; + + if (min_div > gran_div) { + min_short_size *= min_div / gran_div; + } else if (min_div < gran_div) { + unit = min_unit; + gran_short_size *= gran_div / min_div; + } + } virBufferAsprintf(&controlBuf, "<control granularity='%llu'", - bank->controls[j]->granularity >> (min_kilos * 10)); + gran_short_size); - if (bank->controls[j]->min) { - virBufferAsprintf(&controlBuf, - " min='%llu'", - bank->controls[j]->min >> (min_kilos * 10)); - } + if (min_short_size) + virBufferAsprintf(&controlBuf, " min='%llu'", min_short_size); virBufferAsprintf(&controlBuf, " unit='%s' type='%s' maxAllocs='%u'/>\n", - min_kilos ? "KiB" : "B", + unit, virCacheTypeToString(bank->controls[j]->scope), bank->controls[j]->max_allocation); } diff --git a/tests/vircaps2xmldata/vircaps-x86_64-caches.xml b/tests/vircaps2xmldata/vircaps-x86_64-caches.xml index fe0be6d08fa7..0c6f3769a2a7 100644 --- a/tests/vircaps2xmldata/vircaps-x86_64-caches.xml +++ b/tests/vircaps2xmldata/vircaps-x86_64-caches.xml @@ -29,7 +29,7 @@ </cells> </topology> <cache> - <bank id='0' level='3' type='both' size='8192' unit='KiB' cpus='0-7'/> + <bank id='0' level='3' type='both' size='8' unit='MiB' cpus='0-7'/> </cache> </host> diff --git a/tests/vircaps2xmldata/vircaps-x86_64-resctrl-cdp.xml b/tests/vircaps2xmldata/vircaps-x86_64-resctrl-cdp.xml index 7361537bfb56..443917c62d69 100644 --- a/tests/vircaps2xmldata/vircaps-x86_64-resctrl-cdp.xml +++ b/tests/vircaps2xmldata/vircaps-x86_64-resctrl-cdp.xml @@ -41,11 +41,11 @@ </cells> </topology> <cache> - <bank id='0' level='3' type='both' size='15360' unit='KiB' cpus='0-5'> + <bank id='0' level='3' type='both' size='15' unit='MiB' cpus='0-5'> <control granularity='768' unit='KiB' type='code' maxAllocs='8'/> <control granularity='768' unit='KiB' type='data' maxAllocs='8'/> </bank> - <bank id='1' level='3' type='both' size='15360' unit='KiB' cpus='6-11'> + <bank id='1' level='3' type='both' size='15' unit='MiB' cpus='6-11'> <control granularity='768' unit='KiB' type='code' maxAllocs='8'/> <control granularity='768' unit='KiB' type='data' maxAllocs='8'/> </bank> diff --git a/tests/vircaps2xmldata/vircaps-x86_64-resctrl-skx.xml b/tests/vircaps2xmldata/vircaps-x86_64-resctrl-skx.xml index 4e91c87de3b1..0cd25e59a9e0 100644 --- a/tests/vircaps2xmldata/vircaps-x86_64-resctrl-skx.xml +++ b/tests/vircaps2xmldata/vircaps-x86_64-resctrl-skx.xml @@ -22,8 +22,8 @@ </cells> </topology> <cache> - <bank id='0' level='3' type='both' size='33792' unit='KiB' cpus='0'> - <control granularity='3072' unit='KiB' type='both' maxAllocs='16'/> + <bank id='0' level='3' type='both' size='33' unit='MiB' cpus='0'> + <control granularity='3' unit='MiB' type='both' maxAllocs='16'/> </bank> </cache> </host> diff --git a/tests/vircaps2xmldata/vircaps-x86_64-resctrl.xml b/tests/vircaps2xmldata/vircaps-x86_64-resctrl.xml index eb02ad3322a2..7629259294d6 100644 --- a/tests/vircaps2xmldata/vircaps-x86_64-resctrl.xml +++ b/tests/vircaps2xmldata/vircaps-x86_64-resctrl.xml @@ -41,10 +41,10 @@ </cells> </topology> <cache> - <bank id='0' level='3' type='both' size='15360' unit='KiB' cpus='0-5'> + <bank id='0' level='3' type='both' size='15' unit='MiB' cpus='0-5'> <control granularity='768' min='1536' unit='KiB' type='both' maxAllocs='4'/> </bank> - <bank id='1' level='3' type='both' size='15360' unit='KiB' cpus='6-11'> + <bank id='1' level='3' type='both' size='15' unit='MiB' cpus='6-11'> <control granularity='768' min='1536' unit='KiB' type='both' maxAllocs='4'/> </bank> </cache> -- 2.15.0

On 11/13/2017 03:50 AM, Martin Kletzander wrote:
Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- src/conf/capabilities.c | 45 ++++++++++++++-------- tests/vircaps2xmldata/vircaps-x86_64-caches.xml | 2 +- .../vircaps2xmldata/vircaps-x86_64-resctrl-cdp.xml | 4 +- .../vircaps2xmldata/vircaps-x86_64-resctrl-skx.xml | 4 +- tests/vircaps2xmldata/vircaps-x86_64-resctrl.xml | 4 +- 5 files changed, 36 insertions(+), 23 deletions(-)
Reviewed-by: John Ferlan <jferlan@redhat.com> John couple of noisy review thoughts below...
diff --git a/src/conf/capabilities.c b/src/conf/capabilities.c index 095ef51c424a..5bf8ac2019f9 100644 --- a/src/conf/capabilities.c +++ b/src/conf/capabilities.c @@ -883,7 +883,8 @@ virCapabilitiesFormatCaches(virBufferPtr buf, for (i = 0; i < ncaches; i++) { virCapsHostCacheBankPtr bank = caches[i]; char *cpus_str = virBitmapFormat(bank->cpus); - bool kilos = !(bank->size % 1024); + const char *unit = NULL; + unsigned long long short_size = virPrettySize(bank->size, &unit);
if (!cpus_str) return -1; @@ -897,32 +898,44 @@ virCapabilitiesFormatCaches(virBufferPtr buf, "size='%llu' unit='%s' cpus='%s'", bank->id, bank->level, virCacheTypeToString(bank->type), - bank->size >> (kilos * 10), - kilos ? "KiB" : "B", - cpus_str); + short_size, unit, cpus_str); VIR_FREE(cpus_str);
virBufferSetChildIndent(&controlBuf, buf); for (j = 0; j < bank->ncontrols; j++) {
You could have "virResctrlInfoPtr controls = bank->controls[j];"
- bool min_kilos = !(bank->controls[j]->granularity % 1024); - - /* Only use KiB if both values are divisible */ - if (bank->controls[j]->min) - min_kilos = min_kilos && !(bank->controls[j]->min % 1024); + const char *min_unit; + unsigned long long gran_short_size = bank->controls[j]->granularity; + unsigned long long min_short_size = bank->controls[j]->min; + + gran_short_size = virPrettySize(gran_short_size, &unit); + min_short_size = virPrettySize(min_short_size, &min_unit); + + /* Only use the smaller unit if they are different */
Or "if (STRNEQ(unit, min_unit))" - to be more faithful to the comment! I read this as - if min_short_size is there, then we check the unit by knowing the math to get the value. To some degree if the pretty format function allowed one to "choose" a specific format size that'd perhaps work too, but that's perhaps more work than it's worth.
+ if (min_short_size) { + unsigned long long gran_div; + unsigned long long min_div; + + gran_div = bank->controls[j]->granularity / gran_short_size; + min_div = bank->controls[j]->min / min_short_size; + + if (min_div > gran_div) { + min_short_size *= min_div / gran_div; + } else if (min_div < gran_div) { + unit = min_unit; + gran_short_size *= gran_div / min_div; + } + }
virBufferAsprintf(&controlBuf, "<control granularity='%llu'", - bank->controls[j]->granularity >> (min_kilos * 10)); + gran_short_size);
- if (bank->controls[j]->min) { - virBufferAsprintf(&controlBuf, - " min='%llu'", - bank->controls[j]->min >> (min_kilos * 10)); - } + if (min_short_size) + virBufferAsprintf(&controlBuf, " min='%llu'", min_short_size);
virBufferAsprintf(&controlBuf, " unit='%s' type='%s' maxAllocs='%u'/>\n", - min_kilos ? "KiB" : "B", + unit, virCacheTypeToString(bank->controls[j]->scope), bank->controls[j]->max_allocation); } [...]

On Tue, Nov 14, 2017 at 07:38:50AM -0500, John Ferlan wrote:
On 11/13/2017 03:50 AM, Martin Kletzander wrote:
Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- src/conf/capabilities.c | 45 ++++++++++++++-------- tests/vircaps2xmldata/vircaps-x86_64-caches.xml | 2 +- .../vircaps2xmldata/vircaps-x86_64-resctrl-cdp.xml | 4 +- .../vircaps2xmldata/vircaps-x86_64-resctrl-skx.xml | 4 +- tests/vircaps2xmldata/vircaps-x86_64-resctrl.xml | 4 +- 5 files changed, 36 insertions(+), 23 deletions(-)
Reviewed-by: John Ferlan <jferlan@redhat.com>
John
couple of noisy review thoughts below...
diff --git a/src/conf/capabilities.c b/src/conf/capabilities.c index 095ef51c424a..5bf8ac2019f9 100644 --- a/src/conf/capabilities.c +++ b/src/conf/capabilities.c @@ -883,7 +883,8 @@ virCapabilitiesFormatCaches(virBufferPtr buf, for (i = 0; i < ncaches; i++) { virCapsHostCacheBankPtr bank = caches[i]; char *cpus_str = virBitmapFormat(bank->cpus); - bool kilos = !(bank->size % 1024); + const char *unit = NULL; + unsigned long long short_size = virPrettySize(bank->size, &unit);
if (!cpus_str) return -1; @@ -897,32 +898,44 @@ virCapabilitiesFormatCaches(virBufferPtr buf, "size='%llu' unit='%s' cpus='%s'", bank->id, bank->level, virCacheTypeToString(bank->type), - bank->size >> (kilos * 10), - kilos ? "KiB" : "B", - cpus_str); + short_size, unit, cpus_str); VIR_FREE(cpus_str);
virBufferSetChildIndent(&controlBuf, buf); for (j = 0; j < bank->ncontrols; j++) {
You could have "virResctrlInfoPtr controls = bank->controls[j];"
done
- bool min_kilos = !(bank->controls[j]->granularity % 1024); - - /* Only use KiB if both values are divisible */ - if (bank->controls[j]->min) - min_kilos = min_kilos && !(bank->controls[j]->min % 1024); + const char *min_unit; + unsigned long long gran_short_size = bank->controls[j]->granularity; + unsigned long long min_short_size = bank->controls[j]->min; + + gran_short_size = virPrettySize(gran_short_size, &unit); + min_short_size = virPrettySize(min_short_size, &min_unit); + + /* Only use the smaller unit if they are different */
Or "if (STRNEQ(unit, min_unit))" - to be more faithful to the comment! I read this as - if min_short_size is there, then we check the unit by knowing the math to get the value.
To some degree if the pretty format function allowed one to "choose" a specific format size that'd perhaps work too, but that's perhaps more work than it's worth.
if I add STRNEQ there it doesn't allow me to remove any code, it would just add a line. I need the math after that anyway.
+ if (min_short_size) { + unsigned long long gran_div; + unsigned long long min_div; + + gran_div = bank->controls[j]->granularity / gran_short_size; + min_div = bank->controls[j]->min / min_short_size; + + if (min_div > gran_div) { + min_short_size *= min_div / gran_div; + } else if (min_div < gran_div) { + unit = min_unit; + gran_short_size *= gran_div / min_div; + } + }
virBufferAsprintf(&controlBuf, "<control granularity='%llu'", - bank->controls[j]->granularity >> (min_kilos * 10)); + gran_short_size);
- if (bank->controls[j]->min) { - virBufferAsprintf(&controlBuf, - " min='%llu'", - bank->controls[j]->min >> (min_kilos * 10)); - } + if (min_short_size) + virBufferAsprintf(&controlBuf, " min='%llu'", min_short_size);
virBufferAsprintf(&controlBuf, " unit='%s' type='%s' maxAllocs='%u'/>\n", - min_kilos ? "KiB" : "B", + unit, virCacheTypeToString(bank->controls[j]->scope), bank->controls[j]->max_allocation); } [...]

This allows for looking up the cache control information more sensibly from conf/capabilities.c and also provides more data to the virresctrl module that will get more usable later on. Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- po/POTFILES.in | 1 + src/conf/capabilities.c | 48 +++---- src/conf/capabilities.h | 4 +- src/libvirt_private.syms | 4 +- src/util/virresctrl.c | 335 +++++++++++++++++++++++++++++++++-------------- src/util/virresctrl.h | 24 ++-- 6 files changed, 274 insertions(+), 142 deletions(-) diff --git a/po/POTFILES.in b/po/POTFILES.in index c1fa23427eff..8382ee633621 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -252,6 +252,7 @@ src/util/virportallocator.c src/util/virprocess.c src/util/virqemu.c src/util/virrandom.c +src/util/virresctrl.c src/util/virrotatingfile.c src/util/virscsi.c src/util/virscsihost.c diff --git a/src/conf/capabilities.c b/src/conf/capabilities.c index 5bf8ac2019f9..74d8e7459e6d 100644 --- a/src/conf/capabilities.c +++ b/src/conf/capabilities.c @@ -245,6 +245,7 @@ virCapabilitiesDispose(void *object) VIR_FREE(caps->host.netprefix); VIR_FREE(caps->host.pagesSize); virCPUDefFree(caps->host.cpu); + virObjectUnref(caps->host.resctrl); } /** @@ -1588,6 +1589,15 @@ virCapsHostCacheBankSorter(const void *a, const void *b) return ca->id - cb->id; } +static int +virCapabilitiesInitResctrl(virCapsPtr caps) +{ + if (!caps->host.resctrl) + return virResctrlGetInfo(&caps->host.resctrl); + + return 0; +} + int virCapabilitiesInitCaches(virCapsPtr caps) { @@ -1596,7 +1606,6 @@ virCapabilitiesInitCaches(virCapsPtr caps) ssize_t pos = -1; DIR *dirp = NULL; int ret = -1; - int typeret; char *path = NULL; char *type = NULL; struct dirent *ent = NULL; @@ -1607,6 +1616,9 @@ virCapabilitiesInitCaches(virCapsPtr caps) * lose information. */ const int cache_min_level = 3; + if (virCapabilitiesInitResctrl(caps) < 0) + return -1; + /* offline CPUs don't provide cache info */ if (virFileReadValueBitmap(&cpus, "%s/cpu/online", SYSFS_SYSTEM_PATH) < 0) return -1; @@ -1672,32 +1684,6 @@ virCapabilitiesInitCaches(virCapsPtr caps) SYSFS_SYSTEM_PATH, pos, ent->d_name) < 0) goto cleanup; - typeret = virResctrlGetCacheControlType(bank->level); - if (typeret < 0) - goto cleanup; - - if (typeret == 1) { - if (virResctrlGetCacheInfo(bank->level, - bank->size, - VIR_CACHE_TYPE_BOTH, - &bank->controls, - &bank->ncontrols) < 0) - goto cleanup; - } else if (typeret == 2) { - if (virResctrlGetCacheInfo(bank->level, - bank->size, - VIR_CACHE_TYPE_CODE, - &bank->controls, - &bank->ncontrols) < 0) - goto cleanup; - if (virResctrlGetCacheInfo(bank->level, - bank->size, - VIR_CACHE_TYPE_DATA, - &bank->controls, - &bank->ncontrols) < 0) - goto cleanup; - } - kernel_type = virCacheKernelTypeFromString(type); if (kernel_type < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, @@ -1713,6 +1699,14 @@ virCapabilitiesInitCaches(virCapsPtr caps) break; } if (i == caps->host.ncaches) { + /* If it is a new cache, then update its resctrl information. */ + if (virResctrlInfoGetCache(caps->host.resctrl, + bank->level, + bank->size, + &bank->ncontrols, + &bank->controls) < 0) + goto cleanup; + if (VIR_APPEND_ELEMENT(caps->host.caches, caps->host.ncaches, bank) < 0) { diff --git a/src/conf/capabilities.h b/src/conf/capabilities.h index 5048fa819d95..694a3590bf83 100644 --- a/src/conf/capabilities.h +++ b/src/conf/capabilities.h @@ -148,7 +148,7 @@ struct _virCapsHostCacheBank { virCacheType type; /* Data, Instruction or Unified */ virBitmapPtr cpus; /* All CPUs that share this bank */ size_t ncontrols; - virResctrlInfoPtr *controls; + virResctrlInfoPerCachePtr *controls; }; typedef struct _virCapsHost virCapsHost; @@ -170,6 +170,8 @@ struct _virCapsHost { size_t nnumaCell_max; virCapsHostNUMACellPtr *numaCell; + virResctrlInfoPtr resctrl; + size_t ncaches; virCapsHostCacheBankPtr *caches; diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 3986cc523e39..b24728ce4a1d 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2532,8 +2532,8 @@ virRandomInt; # util/virresctrl.h virCacheTypeFromString; virCacheTypeToString; -virResctrlGetCacheControlType; -virResctrlGetCacheInfo; +virResctrlGetInfo; +virResctrlInfoGetCache; # util/virrotatingfile.h diff --git a/src/util/virresctrl.c b/src/util/virresctrl.c index 2a11825a52dc..6c6692e78f42 100644 --- a/src/util/virresctrl.c +++ b/src/util/virresctrl.c @@ -18,6 +18,11 @@ #include <config.h> +#include <sys/file.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + #include "virresctrl.h" #include "c-ctype.h" @@ -25,12 +30,16 @@ #include "viralloc.h" #include "virfile.h" #include "virlog.h" +#include "virobject.h" #include "virstring.h" + #define VIR_FROM_THIS VIR_FROM_RESCTRL VIR_LOG_INIT("util.virresctrl") + +/* Common definitions */ #define SYSFS_RESCTRL_PATH "/sys/fs/resctrl" /* Resctrl is short for Resource Control. It might be implemented for various @@ -55,133 +64,257 @@ VIR_ENUM_IMPL(virResctrl, VIR_CACHE_TYPE_LAST, "CODE", "DATA") -int -virResctrlGetCacheInfo(unsigned int level, - unsigned long long size, - virCacheType scope, - virResctrlInfoPtr **controls, - size_t *ncontrols) -{ - int ret = -1; - char *tmp = NULL; - char *path = NULL; - char *cbm_mask = NULL; - char *type_upper = NULL; - unsigned int bits = 0; - unsigned int min_cbm_bits = 0; - virResctrlInfoPtr control; - - if (VIR_ALLOC(control) < 0) - goto cleanup; - if (scope != VIR_CACHE_TYPE_BOTH && - virStringToUpper(&type_upper, virCacheTypeToString(scope)) < 0) - goto cleanup; +/* Info-related definitions and InfoClass-related functions */ +typedef struct _virResctrlInfoPerType virResctrlInfoPerType; +typedef virResctrlInfoPerType *virResctrlInfoPerTypePtr; +struct _virResctrlInfoPerType { + /* Kernel-provided information */ + char *cbm_mask; + unsigned int min_cbm_bits; - if (virFileReadValueUint(&control->max_allocation, - SYSFS_RESCTRL_PATH "/info/L%u%s/num_closids", - level, - type_upper ? type_upper : "") < 0) - goto cleanup; + /* Our computed information from the above */ + unsigned int bits; + unsigned int max_cache_id; - if (virFileReadValueString(&cbm_mask, - SYSFS_RESCTRL_PATH - "/info/L%u%s/cbm_mask", - level, - type_upper ? type_upper: "") < 0) - goto cleanup; + /* In order to be self-sufficient we need size information per cache. + * Funnily enough, one of the outcomes of the resctrlfs design is that it + * does not account for different sizes per cache on the same level. So + * for the sake of easiness, let's copy that, for now. */ + unsigned long long size; - if (virFileReadValueUint(&min_cbm_bits, - SYSFS_RESCTRL_PATH "/info/L%u%s/min_cbm_bits", - level, - type_upper ? type_upper : "") < 0) - goto cleanup; + /* Information that we will return upon request (this is public struct) as + * until now all the above is internal to this module */ + virResctrlInfoPerCache control; +}; - virStringTrimOptionalNewline(cbm_mask); +typedef struct _virResctrlInfoPerLevel virResctrlInfoPerLevel; +typedef virResctrlInfoPerLevel *virResctrlInfoPerLevelPtr; +struct _virResctrlInfoPerLevel { + virResctrlInfoPerTypePtr *types; +}; - for (tmp = cbm_mask; *tmp != '\0'; tmp++) { - if (c_isxdigit(*tmp)) - bits += count_one_bits(virHexToBin(*tmp)); - } +struct _virResctrlInfo { + virObject parent; - control->granularity = size / bits; - if (min_cbm_bits != 1) - control->min = min_cbm_bits * control->granularity; + virResctrlInfoPerLevelPtr *levels; + size_t nlevels; +}; - control->scope = scope; +static virClassPtr virResctrlInfoClass; - if (VIR_APPEND_ELEMENT(*controls, *ncontrols, control) < 0) - goto cleanup; +static void +virResctrlInfoDispose(void *obj) +{ + size_t i = 0; + size_t j = 0; - ret = 0; + virResctrlInfoPtr resctrl = obj; - cleanup: - VIR_FREE(path); - VIR_FREE(cbm_mask); - VIR_FREE(type_upper); - VIR_FREE(control); - return ret; -} + for (i = 0; i < resctrl->nlevels; i++) { + virResctrlInfoPerLevelPtr level = resctrl->levels[--resctrl->nlevels]; + + if (!level) + continue; + if (level->types) { + for (j = 0; j < VIR_CACHE_TYPE_LAST; j++) + VIR_FREE(level->types[j]); + } + VIR_FREE(level->types); + VIR_FREE(level); + } + + VIR_FREE(resctrl->levels); +} -static inline int -virResctrlGetCacheDir(char **path, - const char *prefix, - unsigned int level, - virCacheType type) +static int virResctrlInfoOnceInit(void) { - return virAsprintf(path, - SYSFS_RESCTRL_PATH "%s/L%u%s", - prefix ? prefix : "", - level, - virResctrlTypeToString(type)); + if (!(virResctrlInfoClass = virClassNew(virClassForObject(), + "virResctrlInfo", + sizeof(virResctrlInfo), + virResctrlInfoDispose))) + return -1; + + return 0; } +VIR_ONCE_GLOBAL_INIT(virResctrlInfo) -/* - * This function tests whether TYPE of cache control is supported or not. - * - * Returns 0 if not, 1 if yes and negative value on error. - */ -static int -virResctrlGetCacheSupport(unsigned int level, virCacheType type) +static virResctrlInfoPtr +virResctrlInfoNew(void) { - int ret = -1; - char *path = NULL; - - if (virResctrlGetCacheDir(&path, "/info", level, type) < 0) - return -1; + if (virResctrlInfoInitialize() < 0) + return NULL; - ret = virFileExists(path); - VIR_FREE(path); - return ret; + return virObjectNew(virResctrlInfoClass); } -/* - * This function tests which TYPE of cache control is supported - * Return values are: - * -1: error - * 0: none - * 1: CAT - * 2: CDP - */ +/* Info-related functions */ int -virResctrlGetCacheControlType(unsigned int level) +virResctrlGetInfo(virResctrlInfoPtr *resctrl) { + DIR *dirp = NULL; + char *info_path = NULL; + char *endptr = NULL; + char *tmp_str = NULL; + int ret = 0; int rv = -1; - - rv = virResctrlGetCacheSupport(level, VIR_CACHE_TYPE_BOTH); - if (rv < 0) + int type = 0; + struct dirent *ent = NULL; + unsigned int level = 0; + virResctrlInfoPerLevelPtr i_level = NULL; + virResctrlInfoPerTypePtr i_type = NULL; + virResctrlInfoPtr tmp_info = virResctrlInfoNew(); + + if (!tmp_info) return -1; - if (rv) - return 1; - rv = virResctrlGetCacheSupport(level, VIR_CACHE_TYPE_CODE); - if (rv < 0) - return -1; - if (rv) - return 2; + rv = virDirOpenIfExists(&dirp, SYSFS_RESCTRL_PATH "/info"); + if (rv <= 0) { + ret = rv; + goto cleanup; + } - return 0; + while ((rv = virDirRead(dirp, &ent, SYSFS_RESCTRL_PATH "/info")) > 0) { + if (ent->d_type != DT_DIR) + continue; + + if (ent->d_name[0] != 'L') + continue; + + if (virStrToLong_uip(ent->d_name + 1, &endptr, 10, &level) < 0) + goto cleanup; + + type = virResctrlTypeFromString(endptr); + if (type < 0) + goto cleanup; + + if (VIR_ALLOC(i_type) < 0) + goto cleanup; + + i_type->control.scope = type; + + if (virFileReadValueUint(&i_type->control.max_allocation, + SYSFS_RESCTRL_PATH "/info/%s/num_closids", + ent->d_name) < 0) + goto cleanup; + + if (virFileReadValueString(&i_type->cbm_mask, + SYSFS_RESCTRL_PATH + "/info/%s/cbm_mask", + ent->d_name) < 0) + goto cleanup; + + if (virFileReadValueUint(&i_type->min_cbm_bits, + SYSFS_RESCTRL_PATH "/info/%s/min_cbm_bits", + ent->d_name) < 0) + goto cleanup; + + virStringTrimOptionalNewline(i_type->cbm_mask); + + if (tmp_info->nlevels <= level && + VIR_EXPAND_N(tmp_info->levels, tmp_info->nlevels, + level - tmp_info->nlevels + 1) < 0) + goto cleanup; + + if (!tmp_info->levels[level] && + (VIR_ALLOC(tmp_info->levels[level]) < 0 || + VIR_ALLOC_N(tmp_info->levels[level]->types, VIR_CACHE_TYPE_LAST) < 0)) + goto cleanup; + i_level = tmp_info->levels[level]; + + if (i_level->types[type]) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Duplicate cache type in resctrlfs for level %u"), + level); + goto cleanup; + } + + if (VIR_ALLOC(i_level->types[type]) < 0) + goto cleanup; + + for (tmp_str = i_type->cbm_mask; *tmp_str != '\0'; tmp_str++) { + if (!c_isxdigit(*tmp_str)) + goto cleanup; + + i_type->bits += count_one_bits(virHexToBin(*tmp_str)); + } + + i_level->types[type] = i_type; + i_type = NULL; + } + + *resctrl = tmp_info; + tmp_info = NULL; + cleanup: + VIR_DIR_CLOSE(dirp); + VIR_FREE(info_path); + virObjectUnref(tmp_info); + VIR_FREE(i_type); + return ret; +} + +int +virResctrlInfoGetCache(virResctrlInfoPtr resctrl, + unsigned int level, + unsigned long long size, + size_t *ncontrols, + virResctrlInfoPerCachePtr **controls) +{ + virResctrlInfoPerLevelPtr i_level = NULL; + virResctrlInfoPerTypePtr i_type = NULL; + size_t i = 0; + int ret = -1; + + if (!resctrl) + return 0; + + if (level >= resctrl->nlevels) + return 0; + + i_level = resctrl->levels[level]; + if (!i_level) + return 0; + + for (i = 0; i < VIR_CACHE_TYPE_LAST; i++) { + i_type = i_level->types[i]; + if (!i_type) + continue; + + /* Let's take the opportunity to update our internal information about + * the cache size */ + if (!i_type->size) { + i_type->size = size; + i_type->control.granularity = size / i_type->bits; + if (i_type->min_cbm_bits != 1) + i_type->control.min = i_type->min_cbm_bits * i_type->control.granularity; + } else { + if (i_type->size != size) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Forbidden inconsistency for resctrlfs, " + "level %u caches have different sizes"), + level); + goto error; + } + i_type->max_cache_id++; + } + + if (VIR_EXPAND_N(*controls, *ncontrols, 1) < 0) + goto error; + if (VIR_ALLOC((*controls)[*ncontrols - 1]) < 0) + goto error; + + memcpy((*controls)[*ncontrols - 1], &i_type->control, sizeof(i_type->control)); + } + + ret = 0; + cleanup: + return ret; + error: + while (*ncontrols) + VIR_FREE((*controls)[--*ncontrols]); + VIR_FREE(*controls); + goto cleanup; } diff --git a/src/util/virresctrl.h b/src/util/virresctrl.h index 848b13e98aa3..c4df88f23c3a 100644 --- a/src/util/virresctrl.h +++ b/src/util/virresctrl.h @@ -36,28 +36,30 @@ typedef enum { VIR_ENUM_DECL(virCache); -typedef struct _virResctrlInfo virResctrlInfo; -typedef virResctrlInfo *virResctrlInfoPtr; -struct _virResctrlInfo { +typedef struct _virResctrlInfoPerCache virResctrlInfoPerCache; +typedef virResctrlInfoPerCache *virResctrlInfoPerCachePtr; +struct _virResctrlInfoPerCache { /* Smallest possible increase of the allocation size in bytes */ unsigned long long granularity; /* Minimal allocatable size in bytes (if different from granularity) */ unsigned long long min; - /* Type of the allocation */ - virCacheType scope; /* Maximum number of simultaneous allocations */ unsigned int max_allocation; + /* Type of the allocation */ + virCacheType scope; }; +typedef struct _virResctrlInfo virResctrlInfo; +typedef virResctrlInfo *virResctrlInfoPtr; int -virResctrlGetCacheInfo(unsigned int level, - unsigned long long size, - virCacheType scope, - virResctrlInfoPtr **controls, - size_t *ncontrols); +virResctrlGetInfo(virResctrlInfoPtr *resctrl); int -virResctrlGetCacheControlType(unsigned int level); +virResctrlInfoGetCache(virResctrlInfoPtr resctrl, + unsigned int level, + unsigned long long size, + size_t *ncontrols, + virResctrlInfoPerCachePtr **controls); #endif /* __VIR_RESCTRL_H__ */ -- 2.15.0

On 11/13/2017 03:50 AM, Martin Kletzander wrote:
This allows for looking up the cache control information more sensibly from conf/capabilities.c and also provides more data to the virresctrl module that will get more usable later on.
Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- po/POTFILES.in | 1 + src/conf/capabilities.c | 48 +++---- src/conf/capabilities.h | 4 +- src/libvirt_private.syms | 4 +- src/util/virresctrl.c | 335 +++++++++++++++++++++++++++++++++-------------- src/util/virresctrl.h | 24 ++-- 6 files changed, 274 insertions(+), 142 deletions(-)
[...]
diff --git a/src/util/virresctrl.c b/src/util/virresctrl.c index 2a11825a52dc..6c6692e78f42 100644 --- a/src/util/virresctrl.c +++ b/src/util/virresctrl.c @@ -18,6 +18,11 @@
#include <config.h>
+#include <sys/file.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + #include "virresctrl.h"
#include "c-ctype.h" @@ -25,12 +30,16 @@ #include "viralloc.h" #include "virfile.h" #include "virlog.h" +#include "virobject.h" #include "virstring.h"
+ #define VIR_FROM_THIS VIR_FROM_RESCTRL
VIR_LOG_INIT("util.virresctrl")
+ +/* Common definitions */ #define SYSFS_RESCTRL_PATH "/sys/fs/resctrl"
/* Resctrl is short for Resource Control. It might be implemented for various @@ -55,133 +64,257 @@ VIR_ENUM_IMPL(virResctrl, VIR_CACHE_TYPE_LAST, "CODE", "DATA")
-int -virResctrlGetCacheInfo(unsigned int level, - unsigned long long size, - virCacheType scope, - virResctrlInfoPtr **controls, - size_t *ncontrols) -{ - int ret = -1; - char *tmp = NULL; - char *path = NULL; - char *cbm_mask = NULL; - char *type_upper = NULL; - unsigned int bits = 0; - unsigned int min_cbm_bits = 0; - virResctrlInfoPtr control; - - if (VIR_ALLOC(control) < 0) - goto cleanup;
- if (scope != VIR_CACHE_TYPE_BOTH && - virStringToUpper(&type_upper, virCacheTypeToString(scope)) < 0) - goto cleanup; +/* Info-related definitions and InfoClass-related functions */ +typedef struct _virResctrlInfoPerType virResctrlInfoPerType; +typedef virResctrlInfoPerType *virResctrlInfoPerTypePtr; +struct _virResctrlInfoPerType { + /* Kernel-provided information */ + char *cbm_mask; + unsigned int min_cbm_bits;
- if (virFileReadValueUint(&control->max_allocation, - SYSFS_RESCTRL_PATH "/info/L%u%s/num_closids", - level, - type_upper ? type_upper : "") < 0) - goto cleanup; + /* Our computed information from the above */ + unsigned int bits; + unsigned int max_cache_id;
- if (virFileReadValueString(&cbm_mask, - SYSFS_RESCTRL_PATH - "/info/L%u%s/cbm_mask", - level, - type_upper ? type_upper: "") < 0) - goto cleanup; + /* In order to be self-sufficient we need size information per cache. + * Funnily enough, one of the outcomes of the resctrlfs design is that it + * does not account for different sizes per cache on the same level. So + * for the sake of easiness, let's copy that, for now. */ + unsigned long long size;
- if (virFileReadValueUint(&min_cbm_bits, - SYSFS_RESCTRL_PATH "/info/L%u%s/min_cbm_bits", - level, - type_upper ? type_upper : "") < 0) - goto cleanup; + /* Information that we will return upon request (this is public struct) as + * until now all the above is internal to this module */ + virResctrlInfoPerCache control; +};
- virStringTrimOptionalNewline(cbm_mask); +typedef struct _virResctrlInfoPerLevel virResctrlInfoPerLevel; +typedef virResctrlInfoPerLevel *virResctrlInfoPerLevelPtr; +struct _virResctrlInfoPerLevel { + virResctrlInfoPerTypePtr *types; +};
- for (tmp = cbm_mask; *tmp != '\0'; tmp++) { - if (c_isxdigit(*tmp)) - bits += count_one_bits(virHexToBin(*tmp)); - } +struct _virResctrlInfo { + virObject parent;
- control->granularity = size / bits; - if (min_cbm_bits != 1) - control->min = min_cbm_bits * control->granularity; + virResctrlInfoPerLevelPtr *levels; + size_t nlevels; +};
- control->scope = scope; +static virClassPtr virResctrlInfoClass;
- if (VIR_APPEND_ELEMENT(*controls, *ncontrols, control) < 0) - goto cleanup; +static void +virResctrlInfoDispose(void *obj) +{ + size_t i = 0; + size_t j = 0;
- ret = 0; + virResctrlInfoPtr resctrl = obj;
- cleanup: - VIR_FREE(path); - VIR_FREE(cbm_mask); - VIR_FREE(type_upper); - VIR_FREE(control); - return ret; -} + for (i = 0; i < resctrl->nlevels; i++) { + virResctrlInfoPerLevelPtr level = resctrl->levels[--resctrl->nlevels];
This decrements the for end loop condition... So this code is going backwards through the levels... I guess it works, but it's one of those "things" about altering the ending of a for loop within the for loop that just "looks strange"... Alternatively: while ((level = resctrl->levels[--resctrl->nlevels])) right? Nothing that I'd ask to change - it just "looks strange"...
+ + if (!level) + continue;
+ if (level->types) { + for (j = 0; j < VIR_CACHE_TYPE_LAST; j++)
level->types[j]->cbm_mask is leaked.
+ VIR_FREE(level->types[j]); + } + VIR_FREE(level->types); + VIR_FREE(level); + } + + VIR_FREE(resctrl->levels); +}
-static inline int -virResctrlGetCacheDir(char **path, - const char *prefix, - unsigned int level, - virCacheType type) +static int virResctrlInfoOnceInit(void) { - return virAsprintf(path, - SYSFS_RESCTRL_PATH "%s/L%u%s", - prefix ? prefix : "", - level, - virResctrlTypeToString(type)); + if (!(virResctrlInfoClass = virClassNew(virClassForObject(), + "virResctrlInfo", + sizeof(virResctrlInfo), + virResctrlInfoDispose)))
The indent is off by 1 for the above 3 lines.
+ return -1; + + return 0; }
+VIR_ONCE_GLOBAL_INIT(virResctrlInfo)
-/* - * This function tests whether TYPE of cache control is supported or not. - * - * Returns 0 if not, 1 if yes and negative value on error. - */ -static int -virResctrlGetCacheSupport(unsigned int level, virCacheType type) +static virResctrlInfoPtr +virResctrlInfoNew(void) { - int ret = -1; - char *path = NULL; - - if (virResctrlGetCacheDir(&path, "/info", level, type) < 0) - return -1; + if (virResctrlInfoInitialize() < 0) + return NULL;
- ret = virFileExists(path); - VIR_FREE(path); - return ret; + return virObjectNew(virResctrlInfoClass); }
-/* - * This function tests which TYPE of cache control is supported - * Return values are: - * -1: error - * 0: none - * 1: CAT - * 2: CDP - */ +/* Info-related functions */ int -virResctrlGetCacheControlType(unsigned int level) +virResctrlGetInfo(virResctrlInfoPtr *resctrl) { + DIR *dirp = NULL; + char *info_path = NULL; + char *endptr = NULL; + char *tmp_str = NULL; + int ret = 0; int rv = -1; - - rv = virResctrlGetCacheSupport(level, VIR_CACHE_TYPE_BOTH); - if (rv < 0) + int type = 0; + struct dirent *ent = NULL; + unsigned int level = 0; + virResctrlInfoPerLevelPtr i_level = NULL; + virResctrlInfoPerTypePtr i_type = NULL; + virResctrlInfoPtr tmp_info = virResctrlInfoNew(); + + if (!tmp_info) return -1; - if (rv) - return 1;
- rv = virResctrlGetCacheSupport(level, VIR_CACHE_TYPE_CODE); - if (rv < 0) - return -1; - if (rv) - return 2; + rv = virDirOpenIfExists(&dirp, SYSFS_RESCTRL_PATH "/info"); + if (rv <= 0) { + ret = rv; + goto cleanup; + }
- return 0; + while ((rv = virDirRead(dirp, &ent, SYSFS_RESCTRL_PATH "/info")) > 0) { + if (ent->d_type != DT_DIR) + continue; + + if (ent->d_name[0] != 'L') + continue; + + if (virStrToLong_uip(ent->d_name + 1, &endptr, 10, &level) < 0) + goto cleanup; + + type = virResctrlTypeFromString(endptr); + if (type < 0) + goto cleanup;> + + if (VIR_ALLOC(i_type) < 0) + goto cleanup; + + i_type->control.scope = type; + + if (virFileReadValueUint(&i_type->control.max_allocation, + SYSFS_RESCTRL_PATH "/info/%s/num_closids", + ent->d_name) < 0) + goto cleanup; + + if (virFileReadValueString(&i_type->cbm_mask, + SYSFS_RESCTRL_PATH + "/info/%s/cbm_mask", + ent->d_name) < 0) + goto cleanup; + + if (virFileReadValueUint(&i_type->min_cbm_bits, + SYSFS_RESCTRL_PATH "/info/%s/min_cbm_bits", + ent->d_name) < 0) + goto cleanup; + + virStringTrimOptionalNewline(i_type->cbm_mask); + + if (tmp_info->nlevels <= level && + VIR_EXPAND_N(tmp_info->levels, tmp_info->nlevels, + level - tmp_info->nlevels + 1) < 0) + goto cleanup; + + if (!tmp_info->levels[level] && + (VIR_ALLOC(tmp_info->levels[level]) < 0 || + VIR_ALLOC_N(tmp_info->levels[level]->types, VIR_CACHE_TYPE_LAST) < 0)) + goto cleanup; + i_level = tmp_info->levels[level]; + + if (i_level->types[type]) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Duplicate cache type in resctrlfs for level %u"), + level); + goto cleanup; + } + + if (VIR_ALLOC(i_level->types[type]) < 0) + goto cleanup; + + for (tmp_str = i_type->cbm_mask; *tmp_str != '\0'; tmp_str++) { + if (!c_isxdigit(*tmp_str)) + goto cleanup; + + i_type->bits += count_one_bits(virHexToBin(*tmp_str)); + } + + i_level->types[type] = i_type; + i_type = NULL; + } + + *resctrl = tmp_info; + tmp_info = NULL;
Is it "by design" to have a number of failures just return 0? My read of the previous code is that it would return some sort of failure if the "dynamic" reading failed. This while loop would ignore failures and "act as if" there was no cache without letting the consumer know. Is that the intent? If so, then should the code also clear the Last error message? Probably should call that out either in the commit message or the beginning of the while loop that if we have a problem, then we're going to "quietly" continue on as if there was no cache. I'm not opposed to continuing on; however, it is different (albeit difficult to see with the diffs enabled - so I've been going back and forth between old/new code).
+ cleanup: + VIR_DIR_CLOSE(dirp); + VIR_FREE(info_path); + virObjectUnref(tmp_info); + VIR_FREE(i_type); + return ret; +} +
Two blank lines between functions OK so I've: Reviewed-by: John Ferlan <jferlan@redhat.com> But obviously there are still questions, still if the design is as I've noted, then carry on... Otherwise, handle/return the error I guess which isn't rocket science. John
+int +virResctrlInfoGetCache(virResctrlInfoPtr resctrl, + unsigned int level, + unsigned long long size, + size_t *ncontrols, + virResctrlInfoPerCachePtr **controls) +{ + virResctrlInfoPerLevelPtr i_level = NULL; + virResctrlInfoPerTypePtr i_type = NULL; + size_t i = 0; + int ret = -1; + + if (!resctrl) + return 0; + + if (level >= resctrl->nlevels) + return 0; + + i_level = resctrl->levels[level]; + if (!i_level) + return 0; + + for (i = 0; i < VIR_CACHE_TYPE_LAST; i++) { + i_type = i_level->types[i]; + if (!i_type) + continue; + + /* Let's take the opportunity to update our internal information about + * the cache size */ + if (!i_type->size) { + i_type->size = size; + i_type->control.granularity = size / i_type->bits; + if (i_type->min_cbm_bits != 1) + i_type->control.min = i_type->min_cbm_bits * i_type->control.granularity; + } else { + if (i_type->size != size) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Forbidden inconsistency for resctrlfs, " + "level %u caches have different sizes"), + level); + goto error; + } + i_type->max_cache_id++; + } + + if (VIR_EXPAND_N(*controls, *ncontrols, 1) < 0) + goto error; + if (VIR_ALLOC((*controls)[*ncontrols - 1]) < 0) + goto error; + + memcpy((*controls)[*ncontrols - 1], &i_type->control, sizeof(i_type->control)); + } + + ret = 0; + cleanup: + return ret; + error: + while (*ncontrols) + VIR_FREE((*controls)[--*ncontrols]); + VIR_FREE(*controls); + goto cleanup; }
[...]

On Tue, Nov 14, 2017 at 10:57:26AM -0500, John Ferlan wrote:
On 11/13/2017 03:50 AM, Martin Kletzander wrote:
This allows for looking up the cache control information more sensibly from conf/capabilities.c and also provides more data to the virresctrl module that will get more usable later on.
Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- po/POTFILES.in | 1 + src/conf/capabilities.c | 48 +++---- src/conf/capabilities.h | 4 +- src/libvirt_private.syms | 4 +- src/util/virresctrl.c | 335 +++++++++++++++++++++++++++++++++-------------- src/util/virresctrl.h | 24 ++-- 6 files changed, 274 insertions(+), 142 deletions(-)
[...]
diff --git a/src/util/virresctrl.c b/src/util/virresctrl.c index 2a11825a52dc..6c6692e78f42 100644 --- a/src/util/virresctrl.c +++ b/src/util/virresctrl.c @@ -18,6 +18,11 @@
#include <config.h>
+#include <sys/file.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + #include "virresctrl.h"
#include "c-ctype.h" @@ -25,12 +30,16 @@ #include "viralloc.h" #include "virfile.h" #include "virlog.h" +#include "virobject.h" #include "virstring.h"
+ #define VIR_FROM_THIS VIR_FROM_RESCTRL
VIR_LOG_INIT("util.virresctrl")
+ +/* Common definitions */ #define SYSFS_RESCTRL_PATH "/sys/fs/resctrl"
/* Resctrl is short for Resource Control. It might be implemented for various @@ -55,133 +64,257 @@ VIR_ENUM_IMPL(virResctrl, VIR_CACHE_TYPE_LAST, "CODE", "DATA")
-int -virResctrlGetCacheInfo(unsigned int level, - unsigned long long size, - virCacheType scope, - virResctrlInfoPtr **controls, - size_t *ncontrols) -{ - int ret = -1; - char *tmp = NULL; - char *path = NULL; - char *cbm_mask = NULL; - char *type_upper = NULL; - unsigned int bits = 0; - unsigned int min_cbm_bits = 0; - virResctrlInfoPtr control; - - if (VIR_ALLOC(control) < 0) - goto cleanup;
- if (scope != VIR_CACHE_TYPE_BOTH && - virStringToUpper(&type_upper, virCacheTypeToString(scope)) < 0) - goto cleanup; +/* Info-related definitions and InfoClass-related functions */ +typedef struct _virResctrlInfoPerType virResctrlInfoPerType; +typedef virResctrlInfoPerType *virResctrlInfoPerTypePtr; +struct _virResctrlInfoPerType { + /* Kernel-provided information */ + char *cbm_mask; + unsigned int min_cbm_bits;
- if (virFileReadValueUint(&control->max_allocation, - SYSFS_RESCTRL_PATH "/info/L%u%s/num_closids", - level, - type_upper ? type_upper : "") < 0) - goto cleanup; + /* Our computed information from the above */ + unsigned int bits; + unsigned int max_cache_id;
- if (virFileReadValueString(&cbm_mask, - SYSFS_RESCTRL_PATH - "/info/L%u%s/cbm_mask", - level, - type_upper ? type_upper: "") < 0) - goto cleanup; + /* In order to be self-sufficient we need size information per cache. + * Funnily enough, one of the outcomes of the resctrlfs design is that it + * does not account for different sizes per cache on the same level. So + * for the sake of easiness, let's copy that, for now. */ + unsigned long long size;
- if (virFileReadValueUint(&min_cbm_bits, - SYSFS_RESCTRL_PATH "/info/L%u%s/min_cbm_bits", - level, - type_upper ? type_upper : "") < 0) - goto cleanup; + /* Information that we will return upon request (this is public struct) as + * until now all the above is internal to this module */ + virResctrlInfoPerCache control; +};
- virStringTrimOptionalNewline(cbm_mask); +typedef struct _virResctrlInfoPerLevel virResctrlInfoPerLevel; +typedef virResctrlInfoPerLevel *virResctrlInfoPerLevelPtr; +struct _virResctrlInfoPerLevel { + virResctrlInfoPerTypePtr *types; +};
- for (tmp = cbm_mask; *tmp != '\0'; tmp++) { - if (c_isxdigit(*tmp)) - bits += count_one_bits(virHexToBin(*tmp)); - } +struct _virResctrlInfo { + virObject parent;
- control->granularity = size / bits; - if (min_cbm_bits != 1) - control->min = min_cbm_bits * control->granularity; + virResctrlInfoPerLevelPtr *levels; + size_t nlevels; +};
- control->scope = scope; +static virClassPtr virResctrlInfoClass;
- if (VIR_APPEND_ELEMENT(*controls, *ncontrols, control) < 0) - goto cleanup; +static void +virResctrlInfoDispose(void *obj) +{ + size_t i = 0; + size_t j = 0;
- ret = 0; + virResctrlInfoPtr resctrl = obj;
- cleanup: - VIR_FREE(path); - VIR_FREE(cbm_mask); - VIR_FREE(type_upper); - VIR_FREE(control); - return ret; -} + for (i = 0; i < resctrl->nlevels; i++) { + virResctrlInfoPerLevelPtr level = resctrl->levels[--resctrl->nlevels];
This decrements the for end loop condition... So this code is going backwards through the levels... I guess it works, but it's one of those "things" about altering the ending of a for loop within the for loop that just "looks strange"... Alternatively:
while ((level = resctrl->levels[--resctrl->nlevels]))
right? Nothing that I'd ask to change - it just "looks strange"...
You're right, this should be: virResctrlInfoPerLevelPtr level = resctrl->levels[i]; It's just leftover from previous version that had: while (resctrl->nlevels)
+ + if (!level) + continue;
+ if (level->types) { + for (j = 0; j < VIR_CACHE_TYPE_LAST; j++)
level->types[j]->cbm_mask is leaked.
Fixed
+ VIR_FREE(level->types[j]); + } + VIR_FREE(level->types); + VIR_FREE(level); + } + + VIR_FREE(resctrl->levels); +}
-static inline int -virResctrlGetCacheDir(char **path, - const char *prefix, - unsigned int level, - virCacheType type) +static int virResctrlInfoOnceInit(void) { - return virAsprintf(path, - SYSFS_RESCTRL_PATH "%s/L%u%s", - prefix ? prefix : "", - level, - virResctrlTypeToString(type)); + if (!(virResctrlInfoClass = virClassNew(virClassForObject(), + "virResctrlInfo", + sizeof(virResctrlInfo), + virResctrlInfoDispose)))
The indent is off by 1 for the above 3 lines.
Good catch, this used to be called virResctrlAllocClass in one of the past versions before "automatic" rename :D Hence the off-by-one indent ;)
+ return -1; + + return 0; }
+VIR_ONCE_GLOBAL_INIT(virResctrlInfo)
-/* - * This function tests whether TYPE of cache control is supported or not. - * - * Returns 0 if not, 1 if yes and negative value on error. - */ -static int -virResctrlGetCacheSupport(unsigned int level, virCacheType type) +static virResctrlInfoPtr +virResctrlInfoNew(void) { - int ret = -1; - char *path = NULL; - - if (virResctrlGetCacheDir(&path, "/info", level, type) < 0) - return -1; + if (virResctrlInfoInitialize() < 0) + return NULL;
- ret = virFileExists(path); - VIR_FREE(path); - return ret; + return virObjectNew(virResctrlInfoClass); }
-/* - * This function tests which TYPE of cache control is supported - * Return values are: - * -1: error - * 0: none - * 1: CAT - * 2: CDP - */ +/* Info-related functions */ int -virResctrlGetCacheControlType(unsigned int level) +virResctrlGetInfo(virResctrlInfoPtr *resctrl) { + DIR *dirp = NULL; + char *info_path = NULL; + char *endptr = NULL; + char *tmp_str = NULL; + int ret = 0; int rv = -1; - - rv = virResctrlGetCacheSupport(level, VIR_CACHE_TYPE_BOTH); - if (rv < 0) + int type = 0; + struct dirent *ent = NULL; + unsigned int level = 0; + virResctrlInfoPerLevelPtr i_level = NULL; + virResctrlInfoPerTypePtr i_type = NULL; + virResctrlInfoPtr tmp_info = virResctrlInfoNew(); + + if (!tmp_info) return -1; - if (rv) - return 1;
- rv = virResctrlGetCacheSupport(level, VIR_CACHE_TYPE_CODE); - if (rv < 0) - return -1; - if (rv) - return 2; + rv = virDirOpenIfExists(&dirp, SYSFS_RESCTRL_PATH "/info"); + if (rv <= 0) { + ret = rv; + goto cleanup; + }
- return 0; + while ((rv = virDirRead(dirp, &ent, SYSFS_RESCTRL_PATH "/info")) > 0) { + if (ent->d_type != DT_DIR) + continue; + + if (ent->d_name[0] != 'L') + continue; + + if (virStrToLong_uip(ent->d_name + 1, &endptr, 10, &level) < 0) + goto cleanup; + + type = virResctrlTypeFromString(endptr); + if (type < 0) + goto cleanup; + + if (VIR_ALLOC(i_type) < 0) + goto cleanup; + + i_type->control.scope = type; + + if (virFileReadValueUint(&i_type->control.max_allocation, + SYSFS_RESCTRL_PATH "/info/%s/num_closids", + ent->d_name) < 0) + goto cleanup; + + if (virFileReadValueString(&i_type->cbm_mask, + SYSFS_RESCTRL_PATH + "/info/%s/cbm_mask", + ent->d_name) < 0) + goto cleanup; + + if (virFileReadValueUint(&i_type->min_cbm_bits, + SYSFS_RESCTRL_PATH "/info/%s/min_cbm_bits", + ent->d_name) < 0) + goto cleanup; + + virStringTrimOptionalNewline(i_type->cbm_mask); + + if (tmp_info->nlevels <= level && + VIR_EXPAND_N(tmp_info->levels, tmp_info->nlevels, + level - tmp_info->nlevels + 1) < 0) + goto cleanup;
Also one more indentation is wrong here. Found by selecting the whole file and letting it auto indent, this was the only missing change, so it should be fine as well. Fixed now.
+ + if (!tmp_info->levels[level] && + (VIR_ALLOC(tmp_info->levels[level]) < 0 || + VIR_ALLOC_N(tmp_info->levels[level]->types, VIR_CACHE_TYPE_LAST) < 0)) + goto cleanup; + i_level = tmp_info->levels[level]; + + if (i_level->types[type]) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Duplicate cache type in resctrlfs for level %u"), + level); + goto cleanup; + } + + if (VIR_ALLOC(i_level->types[type]) < 0) + goto cleanup; + + for (tmp_str = i_type->cbm_mask; *tmp_str != '\0'; tmp_str++) { + if (!c_isxdigit(*tmp_str)) + goto cleanup; + + i_type->bits += count_one_bits(virHexToBin(*tmp_str)); + } + + i_level->types[type] = i_type; + i_type = NULL; + } + + *resctrl = tmp_info; + tmp_info = NULL;
Is it "by design" to have a number of failures just return 0? My read of the previous code is that it would return some sort of failure if the "dynamic" reading failed. This while loop would ignore failures and "act as if" there was no cache without letting the consumer know.
Is that the intent? If so, then should the code also clear the Last error message? Probably should call that out either in the commit message or the beginning of the while loop that if we have a problem, then we're going to "quietly" continue on as if there was no cache.
I'm not opposed to continuing on; however, it is different (albeit difficult to see with the diffs enabled - so I've been going back and forth between old/new code).
Some particular ones should just be "resctrl is not supported here, don't fail starting the daemon because of that". However those are handled, this one should have `int ret = -1;` in the top and `ret = 0;` before the `cleanup:` label of course. Thanks for seeing that. This used to return the pointer to the info previously, that's where the weirdness comes from.
+ cleanup: + VIR_DIR_CLOSE(dirp); + VIR_FREE(info_path); + virObjectUnref(tmp_info); + VIR_FREE(i_type); + return ret; +} +
Two blank lines between functions
OK so I've:
Reviewed-by: John Ferlan <jferlan@redhat.com>
But obviously there are still questions, still if the design is as I've noted, then carry on... Otherwise, handle/return the error I guess which isn't rocket science.
I added virReportError for every situation that did not report the error and silently went for goto cleanup; Also changed the ret value as described above.
John
+int +virResctrlInfoGetCache(virResctrlInfoPtr resctrl, + unsigned int level, + unsigned long long size, + size_t *ncontrols, + virResctrlInfoPerCachePtr **controls) +{ + virResctrlInfoPerLevelPtr i_level = NULL; + virResctrlInfoPerTypePtr i_type = NULL; + size_t i = 0; + int ret = -1; + + if (!resctrl) + return 0; + + if (level >= resctrl->nlevels) + return 0; + + i_level = resctrl->levels[level]; + if (!i_level) + return 0; + + for (i = 0; i < VIR_CACHE_TYPE_LAST; i++) { + i_type = i_level->types[i]; + if (!i_type) + continue; + + /* Let's take the opportunity to update our internal information about + * the cache size */ + if (!i_type->size) { + i_type->size = size; + i_type->control.granularity = size / i_type->bits; + if (i_type->min_cbm_bits != 1) + i_type->control.min = i_type->min_cbm_bits * i_type->control.granularity; + } else { + if (i_type->size != size) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Forbidden inconsistency for resctrlfs, " + "level %u caches have different sizes"), + level); + goto error; + } + i_type->max_cache_id++; + } + + if (VIR_EXPAND_N(*controls, *ncontrols, 1) < 0) + goto error; + if (VIR_ALLOC((*controls)[*ncontrols - 1]) < 0) + goto error; + + memcpy((*controls)[*ncontrols - 1], &i_type->control, sizeof(i_type->control)); + } + + ret = 0; + cleanup: + return ret; + error: + while (*ncontrols) + VIR_FREE((*controls)[--*ncontrols]); + VIR_FREE(*controls); + goto cleanup; }
[...]

On Mon, Nov 13, 2017 at 09:50:27AM +0100, Martin Kletzander wrote:
This allows for looking up the cache control information more sensibly from conf/capabilities.c and also provides more data to the virresctrl module that will get more usable later on.
Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- po/POTFILES.in | 1 + src/conf/capabilities.c | 48 +++---- src/conf/capabilities.h | 4 +- src/libvirt_private.syms | 4 +- src/util/virresctrl.c | 335 +++++++++++++++++++++++++++++++++-------------- src/util/virresctrl.h | 24 ++-- 6 files changed, 274 insertions(+), 142 deletions(-)
In addition to Jonh's review I would split this patch into 3 separate patches: - one is the simple rename of struct _virResctrlInfo to _virResctrlInfoPerCache, - second patch would be the introduction of object virResctrlInfo (preferable with virResctrlInfoObj or similar name) and helper functions to collect that data, - the last one would be the switch to collect the resctrl info separately and use it to update cache information. This one patch itself is really hard to properly review. This is what usually happens if you have the code working and you need to split it into patches :). Otherwise it looks good. Pavel

Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3CODE/cbm_mask | 0 tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3CODE/min_cbm_bits | 0 tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3CODE/num_closids | 0 tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3DATA/cbm_mask | 0 tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3DATA/min_cbm_bits | 0 tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3DATA/num_closids | 0 6 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3CODE/cbm_mask mode change 100755 => 100644 tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3CODE/min_cbm_bits mode change 100755 => 100644 tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3CODE/num_closids mode change 100755 => 100644 tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3DATA/cbm_mask mode change 100755 => 100644 tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3DATA/min_cbm_bits mode change 100755 => 100644 tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3DATA/num_closids diff --git a/tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3CODE/cbm_mask b/tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3CODE/cbm_mask old mode 100755 new mode 100644 diff --git a/tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3CODE/min_cbm_bits b/tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3CODE/min_cbm_bits old mode 100755 new mode 100644 diff --git a/tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3CODE/num_closids b/tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3CODE/num_closids old mode 100755 new mode 100644 diff --git a/tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3DATA/cbm_mask b/tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3DATA/cbm_mask old mode 100755 new mode 100644 diff --git a/tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3DATA/min_cbm_bits b/tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3DATA/min_cbm_bits old mode 100755 new mode 100644 diff --git a/tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3DATA/num_closids b/tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3DATA/num_closids old mode 100755 new mode 100644 -- 2.15.0

On 11/13/2017 03:50 AM, Martin Kletzander wrote:
Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3CODE/cbm_mask | 0 tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3CODE/min_cbm_bits | 0 tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3CODE/num_closids | 0 tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3DATA/cbm_mask | 0 tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3DATA/min_cbm_bits | 0 tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3DATA/num_closids | 0 6 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3CODE/cbm_mask mode change 100755 => 100644 tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3CODE/min_cbm_bits mode change 100755 => 100644 tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3CODE/num_closids mode change 100755 => 100644 tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3DATA/cbm_mask mode change 100755 => 100644 tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3DATA/min_cbm_bits mode change 100755 => 100644 tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/info/L3DATA/num_closids
Reviewed-by: John Ferlan <jferlan@redhat.com> John

Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/schemata | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/schemata b/tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/schemata index 89dc76b86465..5de8fb0261a1 100644 --- a/tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/schemata +++ b/tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/schemata @@ -1,2 +1,2 @@ -L3DATA:0=fffff;1=fffff -L3CODE:0=fffff;1=fffff +L3DATA:0=fffff;1=fc000 +L3CODE:0=ff003;1=f00ff -- 2.15.0

On 11/13/2017 03:50 AM, Martin Kletzander wrote:
Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- tests/vircaps2xmldata/linux-resctrl-cdp/resctrl/schemata | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
I have no idea what the bits represent ;-), still... Reviewed-by: John Ferlan <jferlan@redhat.com> John

Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- .../resctrl/info/L3/cbm_mask | 1 + .../resctrl/info/L3/min_cbm_bits | 1 + .../resctrl/info/L3/num_closids | 1 + .../linux-resctrl-skx-twocaches/resctrl/schemata | 1 + .../resctrl/some_reservation/schemata | 1 + .../system/cpu/cpu0/cache/index0/id | 1 + .../system/cpu/cpu0/cache/index0/level | 1 + .../system/cpu/cpu0/cache/index0/shared_cpu_list | 1 + .../system/cpu/cpu0/cache/index0/shared_cpu_map | 1 + .../system/cpu/cpu0/cache/index0/size | 1 + .../system/cpu/cpu0/cache/index0/type | 1 + .../system/cpu/cpu0/cache/index1/id | 1 + .../system/cpu/cpu0/cache/index1/level | 1 + .../system/cpu/cpu0/cache/index1/shared_cpu_list | 1 + .../system/cpu/cpu0/cache/index1/shared_cpu_map | 1 + .../system/cpu/cpu0/cache/index1/size | 1 + .../system/cpu/cpu0/cache/index1/type | 1 + .../system/cpu/cpu0/online | 1 + .../system/cpu/cpu0/topology/core_id | 1 + .../system/cpu/cpu0/topology/core_siblings | 1 + .../system/cpu/cpu0/topology/core_siblings_list | 1 + .../system/cpu/cpu0/topology/physical_package_id | 1 + .../system/cpu/cpu0/topology/thread_siblings | 1 + .../system/cpu/cpu0/topology/thread_siblings_list | 1 + .../linux-resctrl-skx-twocaches/system/cpu/online | 1 + .../linux-resctrl-skx-twocaches/system/cpu/present | 1 + .../system/node/node0/cpu0 | 1 + .../system/node/node0/cpulist | 1 + .../system/node/node0/cpumap | 1 + .../system/node/node0/distance | 1 + .../linux-resctrl-skx-twocaches/system/node/online | 1 + .../vircaps-x86_64-resctrl-skx-twocaches.xml | 34 ++++++++++++++++++++++ tests/vircaps2xmltest.c | 1 + 33 files changed, 66 insertions(+) create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/resctrl/info/L3/cbm_mask create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/resctrl/info/L3/min_cbm_bits create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/resctrl/info/L3/num_closids create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/resctrl/schemata create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/resctrl/some_reservation/schemata create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/id create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/level create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/shared_cpu_list create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/shared_cpu_map create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/size create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/type create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/id create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/level create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/shared_cpu_list create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/shared_cpu_map create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/size create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/type create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/online create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/core_id create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/core_siblings create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/core_siblings_list create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/physical_package_id create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/thread_siblings create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/thread_siblings_list create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/online create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/present create mode 120000 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/node/node0/cpu0 create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/node/node0/cpulist create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/node/node0/cpumap create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/node/node0/distance create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/node/online create mode 100644 tests/vircaps2xmldata/vircaps-x86_64-resctrl-skx-twocaches.xml diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/resctrl/info/L3/cbm_mask b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/resctrl/info/L3/cbm_mask new file mode 100644 index 000000000000..d482bbb26931 --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/resctrl/info/L3/cbm_mask @@ -0,0 +1 @@ +7ff diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/resctrl/info/L3/min_cbm_bits b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/resctrl/info/L3/min_cbm_bits new file mode 100644 index 000000000000..d00491fd7e5b --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/resctrl/info/L3/min_cbm_bits @@ -0,0 +1 @@ +1 diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/resctrl/info/L3/num_closids b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/resctrl/info/L3/num_closids new file mode 100644 index 000000000000..b6a7d89c68e0 --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/resctrl/info/L3/num_closids @@ -0,0 +1 @@ +16 diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/resctrl/schemata b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/resctrl/schemata new file mode 100644 index 000000000000..691fbaf887d1 --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/resctrl/schemata @@ -0,0 +1 @@ +L3:0=0f0;1=000 diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/resctrl/some_reservation/schemata b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/resctrl/some_reservation/schemata new file mode 100644 index 000000000000..e76adc896cb3 --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/resctrl/some_reservation/schemata @@ -0,0 +1 @@ +L3:0=70e;1=3ff diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/id b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/id new file mode 100644 index 000000000000..573541ac9702 --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/id @@ -0,0 +1 @@ +0 diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/level b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/level new file mode 100644 index 000000000000..00750edc07d6 --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/level @@ -0,0 +1 @@ +3 diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/shared_cpu_list b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/shared_cpu_list new file mode 100644 index 000000000000..573541ac9702 --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/shared_cpu_list @@ -0,0 +1 @@ +0 diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/shared_cpu_map b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/shared_cpu_map new file mode 100644 index 000000000000..5325a8dff751 --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/shared_cpu_map @@ -0,0 +1 @@ +001 diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/size b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/size new file mode 100644 index 000000000000..d23da60c1205 --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/size @@ -0,0 +1 @@ +33M diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/type b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/type new file mode 100644 index 000000000000..e4fd9dccda2c --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/type @@ -0,0 +1 @@ +Unified diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/id b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/id new file mode 100644 index 000000000000..d00491fd7e5b --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/id @@ -0,0 +1 @@ +1 diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/level b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/level new file mode 100644 index 000000000000..00750edc07d6 --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/level @@ -0,0 +1 @@ +3 diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/shared_cpu_list b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/shared_cpu_list new file mode 100644 index 000000000000..573541ac9702 --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/shared_cpu_list @@ -0,0 +1 @@ +0 diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/shared_cpu_map b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/shared_cpu_map new file mode 100644 index 000000000000..5325a8dff751 --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/shared_cpu_map @@ -0,0 +1 @@ +001 diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/size b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/size new file mode 100644 index 000000000000..d23da60c1205 --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/size @@ -0,0 +1 @@ +33M diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/type b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/type new file mode 100644 index 000000000000..e4fd9dccda2c --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/type @@ -0,0 +1 @@ +Unified diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/online b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/online new file mode 100644 index 000000000000..d00491fd7e5b --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/online @@ -0,0 +1 @@ +1 diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/core_id b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/core_id new file mode 100644 index 000000000000..573541ac9702 --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/core_id @@ -0,0 +1 @@ +0 diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/core_siblings b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/core_siblings new file mode 100644 index 000000000000..5325a8dff751 --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/core_siblings @@ -0,0 +1 @@ +001 diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/core_siblings_list b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/core_siblings_list new file mode 100644 index 000000000000..573541ac9702 --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/core_siblings_list @@ -0,0 +1 @@ +0 diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/physical_package_id b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/physical_package_id new file mode 100644 index 000000000000..573541ac9702 --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/physical_package_id @@ -0,0 +1 @@ +0 diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/thread_siblings b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/thread_siblings new file mode 100644 index 000000000000..5325a8dff751 --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/thread_siblings @@ -0,0 +1 @@ +001 diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/thread_siblings_list b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/thread_siblings_list new file mode 100644 index 000000000000..573541ac9702 --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/thread_siblings_list @@ -0,0 +1 @@ +0 diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/online b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/online new file mode 100644 index 000000000000..573541ac9702 --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/online @@ -0,0 +1 @@ +0 diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/present b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/present new file mode 100644 index 000000000000..573541ac9702 --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/present @@ -0,0 +1 @@ +0 diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/node/node0/cpu0 b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/node/node0/cpu0 new file mode 120000 index 000000000000..c841bea28b2b --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/node/node0/cpu0 @@ -0,0 +1 @@ +../../cpu/cpu0 \ No newline at end of file diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/node/node0/cpulist b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/node/node0/cpulist new file mode 100644 index 000000000000..573541ac9702 --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/node/node0/cpulist @@ -0,0 +1 @@ +0 diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/node/node0/cpumap b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/node/node0/cpumap new file mode 100644 index 000000000000..5325a8dff751 --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/node/node0/cpumap @@ -0,0 +1 @@ +001 diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/node/node0/distance b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/node/node0/distance new file mode 100644 index 000000000000..f599e28b8ab0 --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/node/node0/distance @@ -0,0 +1 @@ +10 diff --git a/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/node/online b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/node/online new file mode 100644 index 000000000000..573541ac9702 --- /dev/null +++ b/tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/node/online @@ -0,0 +1 @@ +0 diff --git a/tests/vircaps2xmldata/vircaps-x86_64-resctrl-skx-twocaches.xml b/tests/vircaps2xmldata/vircaps-x86_64-resctrl-skx-twocaches.xml new file mode 100644 index 000000000000..d18665b24fd8 --- /dev/null +++ b/tests/vircaps2xmldata/vircaps-x86_64-resctrl-skx-twocaches.xml @@ -0,0 +1,34 @@ +<capabilities> + + <host> + <cpu> + <arch>x86_64</arch> + </cpu> + <power_management/> + <migration_features> + <live/> + </migration_features> + <topology> + <cells num='1'> + <cell id='0'> + <memory unit='KiB'>1048576</memory> + <pages unit='KiB' size='4'>2048</pages> + <pages unit='KiB' size='2048'>4096</pages> + <pages unit='KiB' size='1048576'>6144</pages> + <cpus num='1'> + <cpu id='0' socket_id='0' core_id='0' siblings='0'/> + </cpus> + </cell> + </cells> + </topology> + <cache> + <bank id='0' level='3' type='both' size='33' unit='MiB' cpus='0'> + <control granularity='3' unit='MiB' type='both' maxAllocs='16'/> + </bank> + <bank id='1' level='3' type='both' size='33' unit='MiB' cpus='0'> + <control granularity='3' unit='MiB' type='both' maxAllocs='16'/> + </bank> + </cache> + </host> + +</capabilities> diff --git a/tests/vircaps2xmltest.c b/tests/vircaps2xmltest.c index 5e8f650c716f..12c43f42c6b9 100644 --- a/tests/vircaps2xmltest.c +++ b/tests/vircaps2xmltest.c @@ -121,6 +121,7 @@ mymain(void) DO_TEST_FULL("resctrl", VIR_ARCH_X86_64, true, true, true); DO_TEST_FULL("resctrl-cdp", VIR_ARCH_X86_64, true, true, true); DO_TEST_FULL("resctrl-skx", VIR_ARCH_X86_64, true, true, true); + DO_TEST_FULL("resctrl-skx-twocaches", VIR_ARCH_X86_64, true, true, true); return ret; } -- 2.15.0

Would be nice to have more information here regarding what this is.... On 11/13/2017 03:50 AM, Martin Kletzander wrote:
Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- .../resctrl/info/L3/cbm_mask | 1 + .../resctrl/info/L3/min_cbm_bits | 1 + .../resctrl/info/L3/num_closids | 1 + .../linux-resctrl-skx-twocaches/resctrl/schemata | 1 + .../resctrl/some_reservation/schemata | 1 + .../system/cpu/cpu0/cache/index0/id | 1 + .../system/cpu/cpu0/cache/index0/level | 1 + .../system/cpu/cpu0/cache/index0/shared_cpu_list | 1 + .../system/cpu/cpu0/cache/index0/shared_cpu_map | 1 + .../system/cpu/cpu0/cache/index0/size | 1 + .../system/cpu/cpu0/cache/index0/type | 1 + .../system/cpu/cpu0/cache/index1/id | 1 + .../system/cpu/cpu0/cache/index1/level | 1 + .../system/cpu/cpu0/cache/index1/shared_cpu_list | 1 + .../system/cpu/cpu0/cache/index1/shared_cpu_map | 1 + .../system/cpu/cpu0/cache/index1/size | 1 + .../system/cpu/cpu0/cache/index1/type | 1 + .../system/cpu/cpu0/online | 1 + .../system/cpu/cpu0/topology/core_id | 1 + .../system/cpu/cpu0/topology/core_siblings | 1 + .../system/cpu/cpu0/topology/core_siblings_list | 1 + .../system/cpu/cpu0/topology/physical_package_id | 1 + .../system/cpu/cpu0/topology/thread_siblings | 1 + .../system/cpu/cpu0/topology/thread_siblings_list | 1 + .../linux-resctrl-skx-twocaches/system/cpu/online | 1 + .../linux-resctrl-skx-twocaches/system/cpu/present | 1 + .../system/node/node0/cpu0 | 1 + .../system/node/node0/cpulist | 1 + .../system/node/node0/cpumap | 1 + .../system/node/node0/distance | 1 + .../linux-resctrl-skx-twocaches/system/node/online | 1 + .../vircaps-x86_64-resctrl-skx-twocaches.xml | 34 ++++++++++++++++++++++ tests/vircaps2xmltest.c | 1 + 33 files changed, 66 insertions(+) create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/resctrl/info/L3/cbm_mask create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/resctrl/info/L3/min_cbm_bits create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/resctrl/info/L3/num_closids create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/resctrl/schemata create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/resctrl/some_reservation/schemata create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/id create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/level create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/shared_cpu_list create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/shared_cpu_map create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/size create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index0/type create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/id create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/level create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/shared_cpu_list create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/shared_cpu_map create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/size create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/cache/index1/type create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/online create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/core_id create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/core_siblings create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/core_siblings_list create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/physical_package_id create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/thread_siblings create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/cpu0/topology/thread_siblings_list create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/online create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/cpu/present create mode 120000 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/node/node0/cpu0 create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/node/node0/cpulist create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/node/node0/cpumap create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/node/node0/distance create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx-twocaches/system/node/online create mode 100644 tests/vircaps2xmldata/vircaps-x86_64-resctrl-skx-twocaches.xml
It seems this is just some new test... It works, but does it add to test bloat? Could it be merged into existing skx test? IOW: does the existing test "need" to have just one bank? I'm OK with the separate test, but I guess it's just not clear why there needs to be a separate one (I could have missed something subtle too). In any case - Reviewed-by: John Ferlan <jferlan@redhat.com> John [apologies for the "timeliness" of reviews - it's been a distracting day... Sometimes it's more worthwhile to just turn off IRC ;-)]

On Tue, Nov 14, 2017 at 04:20:45PM -0500, John Ferlan wrote:
Would be nice to have more information here regarding what this is....
Only after sending have I found out that I somehow missed few commits when adding commit messages. This is one of the examples, another one is "conf: cachetune". I can resend part of the series or just add it as a reply to the mail if you want.
It seems this is just some new test... It works, but does it add to test bloat? Could it be merged into existing skx test? IOW: does the existing test "need" to have just one bank? I'm OK with the separate test, but I guess it's just not clear why there needs to be a separate one (I could have missed something subtle too). In any case -
For example the commit message that's missing here would say that this particular test data actually make future test from patch 19 to have more coverage. There are test cases with comments on why particular cases should fail and I wanted to cover all obvious failures with only a few system<->domain combinations. I would have to find the table and the matrix of all of them that I wrote somewhere on a paper to tell you why exactly this one is needed, but it is written somewhere, maybe it's just in trash =D
Reviewed-by: John Ferlan <jferlan@redhat.com>
John
[apologies for the "timeliness" of reviews - it's been a distracting day... Sometimes it's more worthwhile to just turn off IRC ;-)]
No need to apologize, it's very fast review, I thought I will not get any for couple of weeks =D

With this commit we finally have a way to read and manipulate basic resctrl settings. Locking is done only on exposed functions that read/write from/to resctrlfs. Not in fuctions that are exposed in virresctrlpriv.h as those are only supposed to be used from tests. Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- src/Makefile.am | 2 +- src/libvirt_private.syms | 12 + src/util/virresctrl.c | 1012 ++++++++++++++++++++++++++++++++++++++++++++- src/util/virresctrl.h | 59 +++ src/util/virresctrlpriv.h | 32 ++ 5 files changed, 1115 insertions(+), 2 deletions(-) create mode 100644 src/util/virresctrlpriv.h diff --git a/src/Makefile.am b/src/Makefile.am index 1d24231249de..ad113262fbb0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -167,7 +167,7 @@ UTIL_SOURCES = \ util/virprocess.c util/virprocess.h \ util/virqemu.c util/virqemu.h \ util/virrandom.h util/virrandom.c \ - util/virresctrl.h util/virresctrl.c \ + util/virresctrl.h util/virresctrl.c util/virresctrlpriv.h \ util/virrotatingfile.h util/virrotatingfile.c \ util/virscsi.c util/virscsi.h \ util/virscsihost.c util/virscsihost.h \ diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index b24728ce4a1d..37bac41e618b 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2532,6 +2532,18 @@ virRandomInt; # util/virresctrl.h virCacheTypeFromString; virCacheTypeToString; +virResctrlAllocAddPID; +virResctrlAllocCreate; +virResctrlAllocForeachSize; +virResctrlAllocFormat; +virResctrlAllocGetFree; +virResctrlAllocMasksAssign; +virResctrlAllocNewFromInfo; +virResctrlAllocRemove; +virResctrlAllocSetID; +virResctrlAllocSetSize; +virResctrlAllocUpdateMask; +virResctrlAllocUpdateSize; virResctrlGetInfo; virResctrlInfoGetCache; diff --git a/src/util/virresctrl.c b/src/util/virresctrl.c index 6c6692e78f42..ac1b38436bb2 100644 --- a/src/util/virresctrl.c +++ b/src/util/virresctrl.c @@ -23,7 +23,7 @@ #include <sys/stat.h> #include <fcntl.h> -#include "virresctrl.h" +#include "virresctrlpriv.h" #include "c-ctype.h" #include "count-one-bits.h" @@ -151,6 +151,153 @@ virResctrlInfoNew(void) } +/* Alloc-related definitions and AllocClass-related functions */ +typedef struct _virResctrlAllocPerType virResctrlAllocPerType; +typedef virResctrlAllocPerType *virResctrlAllocPerTypePtr; +struct _virResctrlAllocPerType { + /* There could be bool saying whether this is set or not, but since everything + * in virResctrlAlloc (and most of libvirt) goes with pointer arrays we would + * have to have one more level of allocation anyway, so this stays faithful to + * the concept */ + unsigned long long **sizes; + size_t nsizes; + + /* Mask for each cache */ + virBitmapPtr *masks; + size_t nmasks; +}; + +typedef struct _virResctrlAllocPerLevel virResctrlAllocPerLevel; +typedef virResctrlAllocPerLevel *virResctrlAllocPerLevelPtr; +struct _virResctrlAllocPerLevel { + virResctrlAllocPerTypePtr *types; /* Indexed with enum virCacheType */ +}; + +struct _virResctrlAlloc { + virObject parent; + + virResctrlAllocPerLevelPtr *levels; + size_t nlevels; + + char *id; /* The identifier (any unique string for now) */ + char *path; +}; + +static virClassPtr virResctrlAllocClass; + +static void +virResctrlAllocDispose(void *obj) +{ + size_t i = 0; + size_t j = 0; + size_t k = 0; + + virResctrlAllocPtr resctrl = obj; + + for (i = 0; i < resctrl->nlevels; i++) { + virResctrlAllocPerLevelPtr level = resctrl->levels[--resctrl->nlevels]; + + if (!level) + continue; + + for (j = 0; j < VIR_CACHE_TYPE_LAST; j++) { + virResctrlAllocPerTypePtr type = level->types[j]; + + if (!type) + continue; + + for (k = 0; k < type->nsizes; k++) + VIR_FREE(type->sizes[k]); + + VIR_FREE(type->sizes); + VIR_FREE(type); + } + VIR_FREE(level->types); + VIR_FREE(level); + } + + VIR_FREE(resctrl->id); + VIR_FREE(resctrl->levels); +} + +static int virResctrlAllocOnceInit(void) +{ + if (!(virResctrlAllocClass = virClassNew(virClassForObject(), + "virResctrlAlloc", + sizeof(virResctrlAlloc), + virResctrlAllocDispose))) + return -1; + + return 0; +} + +VIR_ONCE_GLOBAL_INIT(virResctrlAlloc) + +static virResctrlAllocPtr +virResctrlAllocNew(void) +{ + if (virResctrlAllocInitialize() < 0) + return NULL; + + return virObjectNew(virResctrlAllocClass); +} + + +/* Common functions */ +static int +virResctrlLockInternal(int op) +{ + int fd = open(SYSFS_RESCTRL_PATH, O_DIRECTORY | O_CLOEXEC); + + if (fd < 0) { + virReportSystemError(errno, "%s", _("Cannot open resctrlfs")); + return -1; + } + + if (flock(fd, op) < 0) { + virReportSystemError(errno, "%s", _("Cannot lock resctrlfs")); + VIR_FORCE_CLOSE(fd); + return -1; + } + + return fd; +} + +static inline int +virResctrlLockRead(void) +{ + return virResctrlLockInternal(LOCK_SH); +} + +static inline int +virResctrlLockWrite(void) +{ + return virResctrlLockInternal(LOCK_EX); +} + +static int +virResctrlUnlock(int fd) +{ + int ret = -1; + + if (fd == -1) + return 0; + + /* The lock gets unlocked by closing the fd, which we need to do anyway in + * order to clean up properly */ + if (VIR_CLOSE(fd) < 0) { + virReportSystemError(errno, "%s", _("Cannot close resctrlfs")); + + /* Trying to save the already broken */ + if (flock(fd, LOCK_UN) < 0) + virReportSystemError(errno, "%s", _("Cannot unlock resctrlfs")); + return -1; + } + + return ret; +} + + /* Info-related functions */ int virResctrlGetInfo(virResctrlInfoPtr *resctrl) @@ -318,3 +465,866 @@ virResctrlInfoGetCache(virResctrlInfoPtr resctrl, VIR_FREE(*controls); goto cleanup; } + + +/* Alloc-related functions */ +static virResctrlAllocPerTypePtr +virResctrlAllocFindType(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + bool alloc) +{ + virResctrlAllocPerLevelPtr a_level = NULL; + virResctrlAllocPtr tmp = NULL; + + if (!*resctrl) { + if (!alloc || !(*resctrl = virResctrlAllocNew())) + return NULL; + } + + tmp = *resctrl; + + /* Per-level array */ + if (tmp->nlevels <= level) { + if (!alloc || VIR_EXPAND_N(tmp->levels, tmp->nlevels, + level - tmp->nlevels + 1) < 0) + return NULL; + } + + if (!tmp->levels[level]) { + if (!alloc || + VIR_ALLOC(tmp->levels[level]) < 0 || + VIR_ALLOC_N(tmp->levels[level]->types, VIR_CACHE_TYPE_LAST) < 0) + return NULL; + } + a_level = tmp->levels[level]; + + if (!a_level->types[type]) { + if (!alloc || VIR_ALLOC(a_level->types[type]) < 0) + return NULL; + } + + return a_level->types[type]; +} + +static virBitmapPtr * +virResctrlAllocFindMask(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + bool alloc) +{ + virResctrlAllocPerTypePtr a_type = virResctrlAllocFindType(resctrl, level, + type, alloc); + + if (!a_type) + return NULL; + + if (!a_type->masks) { + if (!alloc || VIR_ALLOC_N(a_type->masks, cache + 1) < 0) + return NULL; + a_type->nmasks = cache + 1; + } else if (a_type->nmasks <= cache) { + if (!alloc || VIR_EXPAND_N(a_type->masks, a_type->nmasks, + cache - a_type->nmasks + 1) < 0) + return NULL; + } + + return a_type->masks + cache; +} + +static unsigned long long * +virResctrlAllocFindSize(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + bool alloc) +{ + virResctrlAllocPerTypePtr a_type = virResctrlAllocFindType(resctrl, level, + type, alloc); + + if (!a_type) + return NULL; + + if (!a_type->sizes) { + if (!alloc || VIR_ALLOC_N(a_type->sizes, cache + 1) < 0) + return NULL; + a_type->nsizes = cache + 1; + } else if (a_type->nsizes <= cache) { + if (!alloc || VIR_EXPAND_N(a_type->sizes, a_type->nsizes, + cache - a_type->nsizes + 1) < 0) + return NULL; + } + + if (!a_type->sizes[cache]) { + if (!alloc || VIR_ALLOC(a_type->sizes[cache]) < 0) + return NULL; + } + + return a_type->sizes[cache]; +} + +int +virResctrlAllocUpdateMask(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + virBitmapPtr mask) +{ + virBitmapPtr *found = virResctrlAllocFindMask(resctrl, level, type, cache, + true); + + if (!found) + return -1; + + virBitmapFree(*found); + + *found = virBitmapNew(virBitmapSize(mask)); + if (!*found) + return -1; + + return virBitmapCopy(*found, mask); +} + +int +virResctrlAllocUpdateSize(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + unsigned long long size) +{ + unsigned long long *found = virResctrlAllocFindSize(resctrl, level, type, + cache, true); + + if (!found) + return -1; + + *found = size; + return 0; +} + +static bool +virResctrlAllocCheckCollision(virResctrlAllocPtr a, + unsigned int level, + virCacheType type, + unsigned int cache) +{ + /* If there is an allocation for type 'both', there can be no other + * allocation for the same cache */ + if (virResctrlAllocFindSize(&a, level, VIR_CACHE_TYPE_BOTH, cache, false)) + return true; + + if (type == VIR_CACHE_TYPE_BOTH) { + if (virResctrlAllocFindSize(&a, level, VIR_CACHE_TYPE_CODE, cache, false)) + return true; + if (virResctrlAllocFindSize(&a, level, VIR_CACHE_TYPE_DATA, cache, false)) + return true; + } + + /* And never two allocations for the same type */ + if (virResctrlAllocFindSize(&a, level, type, cache, false)) + return true; + + return false; +} + +int +virResctrlAllocSetSize(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + unsigned long long size) +{ + if (virResctrlAllocCheckCollision(*resctrl, level, type, cache)) { + virReportError(VIR_ERR_XML_ERROR, + _("Colliding cache allocations for cache " + "level '%u' id '%u', type '%s'"), + level, cache, virCacheTypeToString(type)); + return -1; + } + + return virResctrlAllocUpdateSize(resctrl, level, type, cache, size); +} + +int +virResctrlAllocForeachSize(virResctrlAllocPtr resctrl, + virResctrlAllocForeachSizeCallback cb, + void *opaque) +{ + int ret = 0; + unsigned int level = 0; + unsigned int type = 0; + unsigned int cache = 0; + + if (!resctrl) + return 0; + + for (level = 0; level < resctrl->nlevels; level++) { + virResctrlAllocPerLevelPtr a_level = resctrl->levels[level]; + + if (!a_level) + continue; + + for (type = 0; type < VIR_CACHE_TYPE_LAST; type++) { + virResctrlAllocPerTypePtr a_type = a_level->types[type]; + + if (!a_type) + continue; + + for (cache = 0; cache < a_type->nsizes; cache++) { + unsigned long long *size = a_type->sizes[cache]; + + if (!size) + continue; + + ret = cb(level, type, cache, *size, opaque); + if (ret < 0) + return ret; + } + } + } + + return 0; +} + +int +virResctrlAllocSetID(virResctrlAllocPtr alloc, + const char *id) +{ + if (!id) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Resctrl allocation id cannot be NULL")); + return -1; + } + + return VIR_STRDUP(alloc->id, id); +} + +char * +virResctrlAllocFormat(virResctrlAllocPtr resctrl) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + unsigned int level = 0; + unsigned int type = 0; + unsigned int cache = 0; + + if (!resctrl) + return NULL; + + for (level = 0; level < resctrl->nlevels; level++) { + virResctrlAllocPerLevelPtr a_level = resctrl->levels[level]; + + if (!a_level) + continue; + + for (type = 0; type < VIR_CACHE_TYPE_LAST; type++) { + virResctrlAllocPerTypePtr a_type = a_level->types[type]; + + if (!a_type) + continue; + + virBufferAsprintf(&buf, "L%u%s:", level, virResctrlTypeToString(type)); + + for (cache = 0; cache < a_type->nmasks; cache++) { + virBitmapPtr mask = a_type->masks[cache]; + char *mask_str = NULL; + + if (!mask) + continue; + + mask_str = virBitmapToString(mask, false, true); + if (!mask_str) { + virBufferFreeAndReset(&buf); + return NULL; + } + + virBufferAsprintf(&buf, "%u=%s;", cache, mask_str); + } + + virBufferTrim(&buf, ";", 1); + virBufferAddChar(&buf, '\n'); + } + } + + virBufferCheckError(&buf); + return virBufferContentAndReset(&buf); +} + +static int +virResctrlAllocParseProcessCache(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + char *cache) +{ + char *tmp = strchr(cache, '='); + unsigned int cache_id = 0; + virBitmapPtr mask = NULL; + int ret = -1; + + if (!tmp) + return 0; + + *tmp = '\0'; + tmp++; + + if (virStrToLong_uip(cache, NULL, 10, &cache_id) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Invalid cache id '%s'"), cache); + return -1; + } + + mask = virBitmapNewString(tmp); + if (!mask) + return -1; + + if (virResctrlAllocUpdateMask(resctrl, level, type, cache_id, mask) < 0) + goto cleanup; + + ret = 0; + cleanup: + virBitmapFree(mask); + return ret; +} + +static int +virResctrlAllocParseProcessLine(virResctrlAllocPtr *resctrl, + char *line) +{ + char **caches = NULL; + char *tmp = NULL; + unsigned int level = 0; + int type = -1; + size_t ncaches = 0; + size_t i = 0; + int ret = -1; + + /* Skip lines that don't concern caches, e.g. MB: etc. */ + if (line[0] != 'L') + return 0; + + /* And lines that we can't parse too */ + tmp = strchr(line, ':'); + if (!tmp) + return 0; + + *tmp = '\0'; + tmp++; + + if (virStrToLong_uip(line + 1, &line, 10, &level) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Cannot parse resctrl schema level '%s'"), + line + 1); + return -1; + } + + type = virResctrlTypeFromString(line); + if (type < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Cannot parse resctrl schema level '%s'"), + line + 1); + return -1; + } + + caches = virStringSplitCount(tmp, ";", 0, &ncaches); + if (!caches) + return 0; + + for (i = 0; i < ncaches; i++) { + if (virResctrlAllocParseProcessCache(resctrl, level, type, caches[i]) < 0) + goto cleanup; + } + + ret = 0; + cleanup: + virStringListFree(caches); + return ret; +} + +static int +virResctrlAllocParse(virResctrlAllocPtr *alloc, + const char *schemata) +{ + virResctrlAllocPtr tmp = NULL; + char **lines = NULL; + size_t nlines = 0; + size_t i = 0; + int ret = -1; + + lines = virStringSplitCount(schemata, "\n", 0, &nlines); + for (i = 0; i < nlines; i++) { + if (virResctrlAllocParseProcessLine(&tmp, lines[i]) < 0) + goto cleanup; + } + + *alloc = tmp; + tmp = NULL; + ret = 0; + cleanup: + virStringListFree(lines); + virObjectUnref(tmp); + return ret; +} + +static void +virResctrlAllocSubtractPerType(virResctrlAllocPerTypePtr a, + virResctrlAllocPerTypePtr b) +{ + size_t i = 0; + + if (!a || !b) + return; + + for (i = 0; i < a->nmasks && i < b->nmasks; ++i) { + if (a->masks[i] && b->masks[i]) + virBitmapSubtract(a->masks[i], b->masks[i]); + } +} + +static void +virResctrlAllocSubtract(virResctrlAllocPtr a, + virResctrlAllocPtr b) +{ + size_t i = 0; + size_t j = 0; + + if (!b) + return; + + for (i = 0; i < a->nlevels && b->nlevels; ++i) { + if (a->levels[i] && b->levels[i]) { + /* Here we rely on all the system allocations to use the same types. + * We kind of _hope_ it's the case. If this is left here until the + * review and someone finds it, then suggest only removing this last + * sentence. */ + for (j = 0; j < VIR_CACHE_TYPE_LAST; j++) { + virResctrlAllocSubtractPerType(a->levels[i]->types[j], + b->levels[i]->types[j]); + } + } + } +} + +virResctrlAllocPtr +virResctrlAllocNewFromInfo(virResctrlInfoPtr info) +{ + size_t i = 0; + size_t j = 0; + size_t k = 0; + virResctrlAllocPtr ret = NULL; + virBitmapPtr mask = NULL; + + for (i = 0; i < info->nlevels; i++) { + virResctrlInfoPerLevelPtr i_level = info->levels[i]; + + if (!i_level) + continue; + + for (j = 0; j < VIR_CACHE_TYPE_LAST; j++) { + virResctrlInfoPerTypePtr i_type = i_level->types[j]; + + if (!i_type) + continue; + + virBitmapFree(mask); + mask = virBitmapNew(i_type->bits); + if (!mask) + goto error; + virBitmapSetAll(mask); + + for (k = 0; k <= i_type->max_cache_id; k++) { + if (virResctrlAllocUpdateMask(&ret, i, j, k, mask) < 0) + goto error; + } + } + } + + cleanup: + virBitmapFree(mask); + return ret; + error: + virObjectUnref(ret); + ret = NULL; + goto cleanup; +} + +virResctrlAllocPtr +virResctrlAllocGetFree(virResctrlInfoPtr resctrl) +{ + virResctrlAllocPtr ret = NULL; + virResctrlAllocPtr alloc = NULL; + virBitmapPtr mask = NULL; + struct dirent *ent = NULL; + DIR *dirp = NULL; + char *schemata = NULL; + int rv = -1; + + ret = virResctrlAllocNewFromInfo(resctrl); + if (!ret) + return NULL; + + if (virFileReadValueString(&schemata, + SYSFS_RESCTRL_PATH + "/schemata") < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Could not read schemata file for the default group")); + goto error; + } + + if (virResctrlAllocParse(&alloc, schemata) < 0) + goto error; + if (!alloc) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("No schemata for default resctrlfs group")); + goto error; + } + virResctrlAllocSubtract(ret, alloc); + + if (virDirOpen(&dirp, SYSFS_RESCTRL_PATH) < 0) + goto error; + + while ((rv = virDirRead(dirp, &ent, SYSFS_RESCTRL_PATH)) > 0) { + if (ent->d_type != DT_DIR) + continue; + + if (STREQ(ent->d_name, "info")) + continue; + + VIR_FREE(schemata); + if (virFileReadValueString(&schemata, + SYSFS_RESCTRL_PATH + "/%s/schemata", + ent->d_name) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Could not read schemata file for group %s"), + ent->d_name); + goto error; + } + + virObjectUnref(alloc); + alloc = NULL; + if (virResctrlAllocParse(&alloc, schemata) < 0) + goto error; + + virResctrlAllocSubtract(ret, alloc); + + VIR_FREE(schemata); + } + if (rv < 0) + goto error; + + cleanup: + virObjectUnref(alloc); + VIR_DIR_CLOSE(dirp); + VIR_FREE(schemata); + virBitmapFree(mask); + return ret; + + error: + virObjectUnref(ret); + ret = NULL; + goto cleanup; +} + +int +virResctrlAllocMasksAssign(virResctrlInfoPtr r_info, + virResctrlAllocPtr alloc, + void **save_ptr) +{ + int ret = -1; + unsigned int level = 0; + virResctrlAllocPtr alloc_free = NULL; + + if (!r_info) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("Resource control is not supported on this host")); + return -1; + } + + if (!save_ptr) { + alloc_free = virResctrlAllocGetFree(r_info); + } else { + if (!*save_ptr) + *save_ptr = virResctrlAllocGetFree(r_info); + + alloc_free = *save_ptr; + } + + if (!alloc_free) + return -1; + + for (level = 0; level < alloc->nlevels; level++) { + virResctrlAllocPerLevelPtr a_level = alloc->levels[level]; + virResctrlAllocPerLevelPtr f_level = NULL; + unsigned int type = 0; + + if (!a_level) + continue; + + if (level < alloc_free->nlevels) + f_level = alloc_free->levels[level]; + + if (!f_level) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cache level %d does not support tuning"), + level); + goto cleanup; + } + + for (type = 0; type < VIR_CACHE_TYPE_LAST; type++) { + virResctrlAllocPerTypePtr a_type = a_level->types[type]; + virResctrlAllocPerTypePtr f_type = f_level->types[type]; + unsigned int cache = 0; + + if (!a_type) + continue; + + if (!f_type) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cache level %d does not support tuning for " + "scope type '%s'"), + level, virCacheTypeToString(type)); + goto cleanup; + } + + for (cache = 0; cache < a_type->nsizes; cache++) { + unsigned long long *size = a_type->sizes[cache]; + virBitmapPtr a_mask = NULL; + virBitmapPtr f_mask = f_type->masks[cache]; + virResctrlInfoPerLevelPtr i_level = r_info->levels[level]; + virResctrlInfoPerTypePtr i_type = i_level->types[type]; + unsigned long long granularity; + unsigned long long need_bits; + size_t i = 0; + ssize_t pos = -1; + ssize_t last_bits = 0; + ssize_t last_pos = -1; + + if (!size) + continue; + + if (cache >= f_type->nmasks) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cache with id %u does not exists for level %d"), + cache, level); + goto cleanup; + } + + f_mask = f_type->masks[cache]; + if (!f_mask) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cache level %d id %u does not support tuning for " + "scope type '%s'"), + level, cache, virCacheTypeToString(type)); + goto cleanup; + } + + granularity = i_type->size / i_type->bits; + need_bits = *size / granularity; + + if (*size % granularity) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cache allocation of size %llu is not " + "divisible by granularity %llu"), + *size, granularity); + goto cleanup; + } + + if (need_bits < i_type->min_cbm_bits) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cache allocation of size %llu is smaller " + "than the minimum allowed allocation %llu"), + *size, granularity * i_type->min_cbm_bits); + goto cleanup; + } + + while ((pos = virBitmapNextSetBit(f_mask, pos)) >= 0) { + ssize_t pos_clear = virBitmapNextClearBit(f_mask, pos); + ssize_t bits; + + if (pos_clear < 0) + pos_clear = virBitmapSize(f_mask); + + bits = pos_clear - pos; + + /* Not enough bits, move on and skip all of them */ + if (bits < need_bits) { + pos = pos_clear; + continue; + } + + /* This fits perfectly */ + if (bits == need_bits) { + last_pos = pos; + break; + } + + /* Remember the smaller region if we already found on before */ + if (last_pos < 0 || (last_bits && bits < last_bits)) { + last_bits = bits; + last_pos = pos; + } + + pos = pos_clear; + } + + if (last_pos < 0) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Not enough room for allocation of " + "%llu bytes for level %u cache %u " + "scope type '%s'"), + *size, level, cache, + virCacheTypeToString(type)); + goto cleanup; + } + + a_mask = virBitmapNew(i_type->bits); + for (i = last_pos; i < last_pos + need_bits; i++) { + ignore_value(virBitmapSetBit(a_mask, i)); + ignore_value(virBitmapClearBit(f_mask, i)); + } + + if (a_type->nmasks <= cache) { + if (VIR_EXPAND_N(a_type->masks, a_type->nmasks, + cache - a_type->nmasks + 1) < 0) { + virBitmapFree(a_mask); + goto cleanup; + } + } + a_type->masks[cache] = a_mask; + } + } + } + + ret = 0; + cleanup: + if (!save_ptr) + virObjectUnref(alloc_free); + return ret; +} + +/* This checks if the directory for the alloc exists. If not it tries to create + * it and apply appropriate alloc settings. */ +int +virResctrlAllocCreate(virResctrlInfoPtr r_info, + virResctrlAllocPtr alloc, + const char *drivername, + const char *machinename) +{ + char *alloc_path = NULL; + char *schemata_path = NULL; + bool dir_created = false; + char *alloc_str = NULL; + int ret = -1; + int lockfd = -1; + + if (!alloc) + return 0; + + if (!alloc->path && + virAsprintf(&alloc->path, "%s/%s-%s-%s", + SYSFS_RESCTRL_PATH, drivername, machinename, alloc->id) < 0) + return -1; + + /* Check if this allocation was already created */ + if (virFileIsDir(alloc->path)) { + VIR_FREE(alloc_path); + return 0; + } + + if (virFileExists(alloc->path)) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Path '%s' for resctrl allocation exists and is not a " + "directory"), alloc->path); + goto cleanup; + } + + lockfd = virResctrlLockWrite(); + if (lockfd < 0) + goto cleanup; + + if (virResctrlAllocMasksAssign(r_info, alloc, NULL) < 0) + goto cleanup; + + alloc_str = virResctrlAllocFormat(alloc); + if (!alloc_str) + return -1; + + if (virAsprintf(&schemata_path, "%s/schemata", alloc->path) < 0) + goto cleanup; + + if (virFileMakePath(alloc->path) < 0) { + virReportSystemError(errno, + _("Cannot create resctrl directory '%s'"), + alloc->path); + goto cleanup; + } + dir_created = true; + + if (virFileWriteStr(schemata_path, alloc_str, 0) < 0) { + virReportSystemError(errno, + _("Cannot write into schemata file '%s'"), + schemata_path); + goto cleanup; + } + + ret = 0; + cleanup: + if (ret < 0 && dir_created) + rmdir(alloc->path); + virResctrlUnlock(lockfd); + VIR_FREE(alloc_str); + VIR_FREE(alloc_path); + VIR_FREE(schemata_path); + return ret; +} + +int +virResctrlAllocAddPID(virResctrlAllocPtr alloc, + pid_t pid) +{ + char *tasks = NULL; + char *pidstr = NULL; + int ret = 0; + + if (!alloc->path) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Cannot add pid to non-existing resctrl allocation")); + return -1; + } + + if (virAsprintf(&tasks, "%s/tasks", alloc->path) < 0) + return -1; + + if (virAsprintf(&pidstr, "%lld", (long long int) pid) < 0) + goto cleanup; + + if (virFileWriteStr(tasks, pidstr, 0) < 0) { + virReportSystemError(errno, + _("Cannot write pid in tasks file '%s'"), + tasks); + goto cleanup; + } + + ret = 0; + cleanup: + VIR_FREE(tasks); + VIR_FREE(pidstr); + return ret; +} + +int +virResctrlAllocRemove(virResctrlAllocPtr alloc) +{ + int ret = 0; + + if (!alloc->path) + return 0; + + VIR_DEBUG("Removing resctrl allocation %s", alloc->path); + if (rmdir(alloc->path) != 0 && errno != ENOENT) { + ret = -errno; + VIR_ERROR(_("Unable to remove %s (%d)"), alloc->path, errno); + } + + return ret; +} diff --git a/src/util/virresctrl.h b/src/util/virresctrl.h index c4df88f23c3a..b233eca41c03 100644 --- a/src/util/virresctrl.h +++ b/src/util/virresctrl.h @@ -62,4 +62,63 @@ virResctrlInfoGetCache(virResctrlInfoPtr resctrl, size_t *ncontrols, virResctrlInfoPerCachePtr **controls); +/* Alloc-related things */ +typedef struct _virResctrlAlloc virResctrlAlloc; +typedef virResctrlAlloc *virResctrlAllocPtr; + +typedef int virResctrlAllocForeachSizeCallback(unsigned int level, + virCacheType type, + unsigned int cache, + unsigned long long size, + void *opaque); + +virResctrlAllocPtr +virResctrlAllocNewFromInfo(virResctrlInfoPtr info); + +int +virResctrlAllocUpdateSize(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + unsigned long long size); + +int +virResctrlAllocUpdateMask(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + virBitmapPtr mask); + +int +virResctrlAllocSetSize(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + unsigned long long size); + +int +virResctrlAllocForeachSize(virResctrlAllocPtr resctrl, + virResctrlAllocForeachSizeCallback cb, + void *opaque); + +int +virResctrlAllocSetID(virResctrlAllocPtr alloc, + const char *id); + +char * +virResctrlAllocFormat(virResctrlAllocPtr alloc); + +int +virResctrlAllocCreate(virResctrlInfoPtr r_info, + virResctrlAllocPtr alloc, + const char *drivername, + const char *machinename); + +int +virResctrlAllocAddPID(virResctrlAllocPtr alloc, + pid_t pid); + +int +virResctrlAllocRemove(virResctrlAllocPtr alloc); + #endif /* __VIR_RESCTRL_H__ */ diff --git a/src/util/virresctrlpriv.h b/src/util/virresctrlpriv.h new file mode 100644 index 000000000000..4255ad496302 --- /dev/null +++ b/src/util/virresctrlpriv.h @@ -0,0 +1,32 @@ +/* + * virresctrlpriv.h: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + */ + +#ifndef __VIR_RESCTRL_PRIV_H__ +# define __VIR_RESCTRL_PRIV_H__ + +# include "virresctrl.h" + +virResctrlAllocPtr +virResctrlAllocGetFree(virResctrlInfoPtr resctrl); + +int +virResctrlAllocMasksAssign(virResctrlInfoPtr r_info, + virResctrlAllocPtr alloc, + void **save_ptr); + +#endif /* __VIR_RESCTRL_PRIV_H__ */ -- 2.15.0

On 11/13/2017 03:50 AM, Martin Kletzander wrote:
With this commit we finally have a way to read and manipulate basic resctrl settings. Locking is done only on exposed functions that read/write from/to resctrlfs. Not in fuctions that are exposed in virresctrlpriv.h as those are
functions
only supposed to be used from tests.
Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- src/Makefile.am | 2 +- src/libvirt_private.syms | 12 + src/util/virresctrl.c | 1012 ++++++++++++++++++++++++++++++++++++++++++++- src/util/virresctrl.h | 59 +++ src/util/virresctrlpriv.h | 32 ++ 5 files changed, 1115 insertions(+), 2 deletions(-) create mode 100644 src/util/virresctrlpriv.h
This is a *lot* of code! I wasn't able to run through Coverity mainly because I have some stuff in a local branch that conflicts with earlier patches. If you push those, then I can apply these later patches and let Coverity have a peek on memory leaks or other strangeness that I could have missed below. I'll reserve the right to come back here again ;-) I think there's only a few "missed things".
diff --git a/src/Makefile.am b/src/Makefile.am index 1d24231249de..ad113262fbb0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -167,7 +167,7 @@ UTIL_SOURCES = \ util/virprocess.c util/virprocess.h \ util/virqemu.c util/virqemu.h \ util/virrandom.h util/virrandom.c \ - util/virresctrl.h util/virresctrl.c \ + util/virresctrl.h util/virresctrl.c util/virresctrlpriv.h \ util/virrotatingfile.h util/virrotatingfile.c \ util/virscsi.c util/virscsi.h \ util/virscsihost.c util/virscsihost.h \ diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index b24728ce4a1d..37bac41e618b 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2532,6 +2532,18 @@ virRandomInt; # util/virresctrl.h virCacheTypeFromString; virCacheTypeToString; +virResctrlAllocAddPID; +virResctrlAllocCreate; +virResctrlAllocForeachSize; +virResctrlAllocFormat; +virResctrlAllocGetFree; +virResctrlAllocMasksAssign; +virResctrlAllocNewFromInfo; +virResctrlAllocRemove; +virResctrlAllocSetID; +virResctrlAllocSetSize; +virResctrlAllocUpdateMask; +virResctrlAllocUpdateSize; virResctrlGetInfo; virResctrlInfoGetCache;
diff --git a/src/util/virresctrl.c b/src/util/virresctrl.c index 6c6692e78f42..ac1b38436bb2 100644 --- a/src/util/virresctrl.c +++ b/src/util/virresctrl.c @@ -23,7 +23,7 @@ #include <sys/stat.h> #include <fcntl.h>
-#include "virresctrl.h" +#include "virresctrlpriv.h"
#include "c-ctype.h" #include "count-one-bits.h" @@ -151,6 +151,153 @@ virResctrlInfoNew(void) }
+/* Alloc-related definitions and AllocClass-related functions */ +typedef struct _virResctrlAllocPerType virResctrlAllocPerType; +typedef virResctrlAllocPerType *virResctrlAllocPerTypePtr; +struct _virResctrlAllocPerType { + /* There could be bool saying whether this is set or not, but since everything + * in virResctrlAlloc (and most of libvirt) goes with pointer arrays we would + * have to have one more level of allocation anyway, so this stays faithful to + * the concept */ + unsigned long long **sizes; + size_t nsizes; + + /* Mask for each cache */ + virBitmapPtr *masks; + size_t nmasks; +}; + +typedef struct _virResctrlAllocPerLevel virResctrlAllocPerLevel; +typedef virResctrlAllocPerLevel *virResctrlAllocPerLevelPtr; +struct _virResctrlAllocPerLevel { + virResctrlAllocPerTypePtr *types; /* Indexed with enum virCacheType */ +}; + +struct _virResctrlAlloc { + virObject parent; + + virResctrlAllocPerLevelPtr *levels; + size_t nlevels; + + char *id; /* The identifier (any unique string for now) */ + char *path; +}; + +static virClassPtr virResctrlAllocClass; + +static void +virResctrlAllocDispose(void *obj) +{ + size_t i = 0; + size_t j = 0; + size_t k = 0; + + virResctrlAllocPtr resctrl = obj; + + for (i = 0; i < resctrl->nlevels; i++) { + virResctrlAllocPerLevelPtr level = resctrl->levels[--resctrl->nlevels];
Again the odd (to me at least) looking loop control that's reducing the for loop end condition.
+ + if (!level) + continue; + + for (j = 0; j < VIR_CACHE_TYPE_LAST; j++) { + virResctrlAllocPerTypePtr type = level->types[j]; + + if (!type) + continue; + + for (k = 0; k < type->nsizes; k++) + VIR_FREE(type->sizes[k]); + + VIR_FREE(type->sizes);
what about type->masks[k] You could create a Free function for each entry too.
+ VIR_FREE(type); + } + VIR_FREE(level->types); + VIR_FREE(level); + } + + VIR_FREE(resctrl->id);
resctrl->path ?
+ VIR_FREE(resctrl->levels); +} +
Two blank lines (multiple instances in new functions)
+static int virResctrlAllocOnceInit(void)
static int virResctrlAllocOnceInit(void)
+{ + if (!(virResctrlAllocClass = virClassNew(virClassForObject(), + "virResctrlAlloc", + sizeof(virResctrlAlloc), + virResctrlAllocDispose))) + return -1; + + return 0; +} + +VIR_ONCE_GLOBAL_INIT(virResctrlAlloc) + +static virResctrlAllocPtr +virResctrlAllocNew(void) +{ + if (virResctrlAllocInitialize() < 0) + return NULL; + + return virObjectNew(virResctrlAllocClass); +} + + +/* Common functions */ +static int +virResctrlLockInternal(int op) +{ + int fd = open(SYSFS_RESCTRL_PATH, O_DIRECTORY | O_CLOEXEC); + + if (fd < 0) { + virReportSystemError(errno, "%s", _("Cannot open resctrlfs")); + return -1; + } + + if (flock(fd, op) < 0) {
So only ever used on a local file system right? Linux file locking functions are confounding... Why not use virFile{Lock|Unlock}?
+ virReportSystemError(errno, "%s", _("Cannot lock resctrlfs")); + VIR_FORCE_CLOSE(fd); + return -1; + } + + return fd; +} + +static inline int +virResctrlLockRead(void)
Not used in this series...
+{ + return virResctrlLockInternal(LOCK_SH); +} + +static inline int +virResctrlLockWrite(void) +{ + return virResctrlLockInternal(LOCK_EX); +} + +static int +virResctrlUnlock(int fd) +{ + int ret = -1; + + if (fd == -1) + return 0; + + /* The lock gets unlocked by closing the fd, which we need to do anyway in + * order to clean up properly */ + if (VIR_CLOSE(fd) < 0) { + virReportSystemError(errno, "%s", _("Cannot close resctrlfs")); + + /* Trying to save the already broken */
So if close unlocks too, then why the unlock?
+ if (flock(fd, LOCK_UN) < 0) + virReportSystemError(errno, "%s", _("Cannot unlock resctrlfs")); + return -1; + } + + return ret; +} + + /* Info-related functions */ int virResctrlGetInfo(virResctrlInfoPtr *resctrl) @@ -318,3 +465,866 @@ virResctrlInfoGetCache(virResctrlInfoPtr resctrl, VIR_FREE(*controls); goto cleanup; } + + +/* Alloc-related functions */
A few notes about the arguments could be beneficial... Or at least the algorithm that allows partial allocations to work for future consumers.
+static virResctrlAllocPerTypePtr +virResctrlAllocFindType(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + bool alloc) +{ + virResctrlAllocPerLevelPtr a_level = NULL; + virResctrlAllocPtr tmp = NULL; + + if (!*resctrl) { + if (!alloc || !(*resctrl = virResctrlAllocNew())) + return NULL; + } + + tmp = *resctrl; + + /* Per-level array */ + if (tmp->nlevels <= level) { + if (!alloc || VIR_EXPAND_N(tmp->levels, tmp->nlevels, + level - tmp->nlevels + 1) < 0) + return NULL; + } + + if (!tmp->levels[level]) { + if (!alloc || + VIR_ALLOC(tmp->levels[level]) < 0 || + VIR_ALLOC_N(tmp->levels[level]->types, VIR_CACHE_TYPE_LAST) < 0) + return NULL; + } + a_level = tmp->levels[level]; + + if (!a_level->types[type]) { + if (!alloc || VIR_ALLOC(a_level->types[type]) < 0) + return NULL; + } + + return a_level->types[type]; +} + +static virBitmapPtr * +virResctrlAllocFindMask(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + bool alloc) +{ + virResctrlAllocPerTypePtr a_type = virResctrlAllocFindType(resctrl, level, + type, alloc); + + if (!a_type) + return NULL; + + if (!a_type->masks) { + if (!alloc || VIR_ALLOC_N(a_type->masks, cache + 1) < 0) + return NULL; + a_type->nmasks = cache + 1; + } else if (a_type->nmasks <= cache) { + if (!alloc || VIR_EXPAND_N(a_type->masks, a_type->nmasks, + cache - a_type->nmasks + 1) < 0) + return NULL; + } + + return a_type->masks + cache; +} + +static unsigned long long * +virResctrlAllocFindSize(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + bool alloc) +{ + virResctrlAllocPerTypePtr a_type = virResctrlAllocFindType(resctrl, level, + type, alloc); + + if (!a_type) + return NULL; + + if (!a_type->sizes) { + if (!alloc || VIR_ALLOC_N(a_type->sizes, cache + 1) < 0) + return NULL; + a_type->nsizes = cache + 1; + } else if (a_type->nsizes <= cache) { + if (!alloc || VIR_EXPAND_N(a_type->sizes, a_type->nsizes, + cache - a_type->nsizes + 1) < 0) + return NULL; + } + + if (!a_type->sizes[cache]) { + if (!alloc || VIR_ALLOC(a_type->sizes[cache]) < 0) + return NULL; + } + + return a_type->sizes[cache]; +} +
This could really use a functional description especially since it's external... Interesting way to code this though - took a few attempts to stare at it before it finally started sinking in ;-)
+int +virResctrlAllocUpdateMask(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + virBitmapPtr mask) +{ + virBitmapPtr *found = virResctrlAllocFindMask(resctrl, level, type, cache, + true); + + if (!found) + return -1; + + virBitmapFree(*found); + + *found = virBitmapNew(virBitmapSize(mask)); + if (!*found) + return -1; + + return virBitmapCopy(*found, mask);> +} +
Similarly here too... I think "external" function should be commented. I'll only mention again here though...
+int +virResctrlAllocUpdateSize(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + unsigned long long size) +{ + unsigned long long *found = virResctrlAllocFindSize(resctrl, level, type, + cache, true); + + if (!found) + return -1; + + *found = size; + return 0; +} + +static bool +virResctrlAllocCheckCollision(virResctrlAllocPtr a, + unsigned int level, + virCacheType type, + unsigned int cache) +{ + /* If there is an allocation for type 'both', there can be no other + * allocation for the same cache */ + if (virResctrlAllocFindSize(&a, level, VIR_CACHE_TYPE_BOTH, cache, false)) + return true; + + if (type == VIR_CACHE_TYPE_BOTH) { + if (virResctrlAllocFindSize(&a, level, VIR_CACHE_TYPE_CODE, cache, false)) + return true; + if (virResctrlAllocFindSize(&a, level, VIR_CACHE_TYPE_DATA, cache, false)) + return true; + } + + /* And never two allocations for the same type */ + if (virResctrlAllocFindSize(&a, level, type, cache, false)) + return true; + + return false; +} + +int +virResctrlAllocSetSize(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + unsigned long long size) +{ + if (virResctrlAllocCheckCollision(*resctrl, level, type, cache)) { + virReportError(VIR_ERR_XML_ERROR, + _("Colliding cache allocations for cache " + "level '%u' id '%u', type '%s'"), + level, cache, virCacheTypeToString(type)); + return -1; + } + + return virResctrlAllocUpdateSize(resctrl, level, type, cache, size); +} + +int +virResctrlAllocForeachSize(virResctrlAllocPtr resctrl, + virResctrlAllocForeachSizeCallback cb, + void *opaque) +{ + int ret = 0; + unsigned int level = 0; + unsigned int type = 0; + unsigned int cache = 0; + + if (!resctrl) + return 0; + + for (level = 0; level < resctrl->nlevels; level++) { + virResctrlAllocPerLevelPtr a_level = resctrl->levels[level]; + + if (!a_level) + continue; + + for (type = 0; type < VIR_CACHE_TYPE_LAST; type++) { + virResctrlAllocPerTypePtr a_type = a_level->types[type]; + + if (!a_type) + continue; + + for (cache = 0; cache < a_type->nsizes; cache++) { + unsigned long long *size = a_type->sizes[cache]; + + if (!size) + continue; + + ret = cb(level, type, cache, *size, opaque); + if (ret < 0) + return ret; + } + } + } + + return 0; +} + +int +virResctrlAllocSetID(virResctrlAllocPtr alloc, + const char *id) +{ + if (!id) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Resctrl allocation id cannot be NULL")); + return -1; + } + + return VIR_STRDUP(alloc->id, id); +} + +char * +virResctrlAllocFormat(virResctrlAllocPtr resctrl)
No external consumer yet.
+{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + unsigned int level = 0; + unsigned int type = 0; + unsigned int cache = 0; + + if (!resctrl) + return NULL; + + for (level = 0; level < resctrl->nlevels; level++) { + virResctrlAllocPerLevelPtr a_level = resctrl->levels[level]; + + if (!a_level) + continue; + + for (type = 0; type < VIR_CACHE_TYPE_LAST; type++) { + virResctrlAllocPerTypePtr a_type = a_level->types[type]; + + if (!a_type) + continue; + + virBufferAsprintf(&buf, "L%u%s:", level, virResctrlTypeToString(type)); + + for (cache = 0; cache < a_type->nmasks; cache++) { + virBitmapPtr mask = a_type->masks[cache]; + char *mask_str = NULL; + + if (!mask) + continue; + + mask_str = virBitmapToString(mask, false, true); + if (!mask_str) { + virBufferFreeAndReset(&buf); + return NULL; + } + + virBufferAsprintf(&buf, "%u=%s;", cache, mask_str); + } + + virBufferTrim(&buf, ";", 1); + virBufferAddChar(&buf, '\n'); + } + } + + virBufferCheckError(&buf); + return virBufferContentAndReset(&buf); +} + +static int +virResctrlAllocParseProcessCache(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + char *cache) +{ + char *tmp = strchr(cache, '='); + unsigned int cache_id = 0; + virBitmapPtr mask = NULL; + int ret = -1; + + if (!tmp) + return 0; + + *tmp = '\0'; + tmp++; + + if (virStrToLong_uip(cache, NULL, 10, &cache_id) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Invalid cache id '%s'"), cache); + return -1; + } + + mask = virBitmapNewString(tmp); + if (!mask) + return -1; + + if (virResctrlAllocUpdateMask(resctrl, level, type, cache_id, mask) < 0) + goto cleanup; + + ret = 0; + cleanup: + virBitmapFree(mask); + return ret; +} + +static int +virResctrlAllocParseProcessLine(virResctrlAllocPtr *resctrl, + char *line) +{ + char **caches = NULL; + char *tmp = NULL; + unsigned int level = 0; + int type = -1; + size_t ncaches = 0; + size_t i = 0; + int ret = -1; + + /* Skip lines that don't concern caches, e.g. MB: etc. */ + if (line[0] != 'L') + return 0; + + /* And lines that we can't parse too */ + tmp = strchr(line, ':'); + if (!tmp) + return 0; + + *tmp = '\0'; + tmp++; + + if (virStrToLong_uip(line + 1, &line, 10, &level) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Cannot parse resctrl schema level '%s'"), + line + 1); + return -1; + } + + type = virResctrlTypeFromString(line); + if (type < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Cannot parse resctrl schema level '%s'"), + line + 1); + return -1; + } + + caches = virStringSplitCount(tmp, ";", 0, &ncaches); + if (!caches) + return 0; + + for (i = 0; i < ncaches; i++) { + if (virResctrlAllocParseProcessCache(resctrl, level, type, caches[i]) < 0) + goto cleanup; + } + + ret = 0; + cleanup: + virStringListFree(caches); + return ret; +} + +static int +virResctrlAllocParse(virResctrlAllocPtr *alloc, + const char *schemata) +{ + virResctrlAllocPtr tmp = NULL; + char **lines = NULL; + size_t nlines = 0; + size_t i = 0; + int ret = -1; + + lines = virStringSplitCount(schemata, "\n", 0, &nlines); + for (i = 0; i < nlines; i++) { + if (virResctrlAllocParseProcessLine(&tmp, lines[i]) < 0) + goto cleanup; + } + + *alloc = tmp; + tmp = NULL; + ret = 0; + cleanup: + virStringListFree(lines); + virObjectUnref(tmp); + return ret; +} + +static void +virResctrlAllocSubtractPerType(virResctrlAllocPerTypePtr a, + virResctrlAllocPerTypePtr b) +{ + size_t i = 0; + + if (!a || !b) + return; + + for (i = 0; i < a->nmasks && i < b->nmasks; ++i) { + if (a->masks[i] && b->masks[i]) + virBitmapSubtract(a->masks[i], b->masks[i]); + } +} + +static void +virResctrlAllocSubtract(virResctrlAllocPtr a, + virResctrlAllocPtr b) +{ + size_t i = 0; + size_t j = 0; + + if (!b) + return; + + for (i = 0; i < a->nlevels && b->nlevels; ++i) { + if (a->levels[i] && b->levels[i]) { + /* Here we rely on all the system allocations to use the same types. + * We kind of _hope_ it's the case. If this is left here until the + * review and someone finds it, then suggest only removing this last + * sentence. */
Should we use 'sa_assert' instead of just hoping? it's not the verboten assert, but at least the Coverity or Clang would catch, right?
+ for (j = 0; j < VIR_CACHE_TYPE_LAST; j++) { + virResctrlAllocSubtractPerType(a->levels[i]->types[j], + b->levels[i]->types[j]); + } + } + } +} + +virResctrlAllocPtr +virResctrlAllocNewFromInfo(virResctrlInfoPtr info)
No external consumer
+{ + size_t i = 0; + size_t j = 0; + size_t k = 0; + virResctrlAllocPtr ret = NULL; + virBitmapPtr mask = NULL; + + for (i = 0; i < info->nlevels; i++) { + virResctrlInfoPerLevelPtr i_level = info->levels[i]; + + if (!i_level) + continue; + + for (j = 0; j < VIR_CACHE_TYPE_LAST; j++) { + virResctrlInfoPerTypePtr i_type = i_level->types[j]; + + if (!i_type) + continue; + + virBitmapFree(mask); + mask = virBitmapNew(i_type->bits); + if (!mask) + goto error; + virBitmapSetAll(mask); + + for (k = 0; k <= i_type->max_cache_id; k++) { + if (virResctrlAllocUpdateMask(&ret, i, j, k, mask) < 0) + goto error; + } + } + } + + cleanup: + virBitmapFree(mask); + return ret; + error: + virObjectUnref(ret); + ret = NULL; + goto cleanup; +} + +virResctrlAllocPtr +virResctrlAllocGetFree(virResctrlInfoPtr resctrl)
No external consumer...
+{ + virResctrlAllocPtr ret = NULL; + virResctrlAllocPtr alloc = NULL; + virBitmapPtr mask = NULL; + struct dirent *ent = NULL; + DIR *dirp = NULL; + char *schemata = NULL; + int rv = -1; + + ret = virResctrlAllocNewFromInfo(resctrl); + if (!ret) + return NULL; + + if (virFileReadValueString(&schemata, + SYSFS_RESCTRL_PATH + "/schemata") < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Could not read schemata file for the default group")); + goto error; + } + + if (virResctrlAllocParse(&alloc, schemata) < 0) + goto error; + if (!alloc) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("No schemata for default resctrlfs group")); + goto error; + } + virResctrlAllocSubtract(ret, alloc); + + if (virDirOpen(&dirp, SYSFS_RESCTRL_PATH) < 0) + goto error; + + while ((rv = virDirRead(dirp, &ent, SYSFS_RESCTRL_PATH)) > 0) { + if (ent->d_type != DT_DIR) + continue; + + if (STREQ(ent->d_name, "info")) + continue; + + VIR_FREE(schemata); + if (virFileReadValueString(&schemata, + SYSFS_RESCTRL_PATH + "/%s/schemata", + ent->d_name) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Could not read schemata file for group %s"), + ent->d_name); + goto error; + } + + virObjectUnref(alloc); + alloc = NULL; + if (virResctrlAllocParse(&alloc, schemata) < 0) + goto error; + + virResctrlAllocSubtract(ret, alloc); + + VIR_FREE(schemata); + } + if (rv < 0) + goto error; + + cleanup: + virObjectUnref(alloc); + VIR_DIR_CLOSE(dirp); + VIR_FREE(schemata); + virBitmapFree(mask); + return ret; + + error: + virObjectUnref(ret); + ret = NULL; + goto cleanup; +} + +int +virResctrlAllocMasksAssign(virResctrlInfoPtr r_info, + virResctrlAllocPtr alloc, + void **save_ptr)
No external consumer...
+{ + int ret = -1; + unsigned int level = 0; + virResctrlAllocPtr alloc_free = NULL; + + if (!r_info) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("Resource control is not supported on this host")); + return -1; + } + + if (!save_ptr) { + alloc_free = virResctrlAllocGetFree(r_info); + } else { + if (!*save_ptr) + *save_ptr = virResctrlAllocGetFree(r_info); + + alloc_free = *save_ptr; + } + + if (!alloc_free) + return -1; + + for (level = 0; level < alloc->nlevels; level++) { + virResctrlAllocPerLevelPtr a_level = alloc->levels[level]; + virResctrlAllocPerLevelPtr f_level = NULL; + unsigned int type = 0; + + if (!a_level) + continue; + + if (level < alloc_free->nlevels) + f_level = alloc_free->levels[level]; + + if (!f_level) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cache level %d does not support tuning"), + level); + goto cleanup; + } + + for (type = 0; type < VIR_CACHE_TYPE_LAST; type++) {
OMG, my eyes need a beer!
+ virResctrlAllocPerTypePtr a_type = a_level->types[type]; + virResctrlAllocPerTypePtr f_type = f_level->types[type]; + unsigned int cache = 0; + + if (!a_type) + continue; + + if (!f_type) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cache level %d does not support tuning for " + "scope type '%s'"), + level, virCacheTypeToString(type)); + goto cleanup; + } + + for (cache = 0; cache < a_type->nsizes; cache++) { + unsigned long long *size = a_type->sizes[cache]; + virBitmapPtr a_mask = NULL; + virBitmapPtr f_mask = f_type->masks[cache]; + virResctrlInfoPerLevelPtr i_level = r_info->levels[level]; + virResctrlInfoPerTypePtr i_type = i_level->types[type]; + unsigned long long granularity; + unsigned long long need_bits; + size_t i = 0; + ssize_t pos = -1; + ssize_t last_bits = 0; + ssize_t last_pos = -1; + + if (!size) + continue; + + if (cache >= f_type->nmasks) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cache with id %u does not exists for level %d"), + cache, level); + goto cleanup; + } + + f_mask = f_type->masks[cache]; + if (!f_mask) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cache level %d id %u does not support tuning for " + "scope type '%s'"), + level, cache, virCacheTypeToString(type)); + goto cleanup; + } + + granularity = i_type->size / i_type->bits; + need_bits = *size / granularity; + + if (*size % granularity) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cache allocation of size %llu is not " + "divisible by granularity %llu"), + *size, granularity); + goto cleanup; + } + + if (need_bits < i_type->min_cbm_bits) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cache allocation of size %llu is smaller " + "than the minimum allowed allocation %llu"), + *size, granularity * i_type->min_cbm_bits); + goto cleanup; + } + + while ((pos = virBitmapNextSetBit(f_mask, pos)) >= 0) { + ssize_t pos_clear = virBitmapNextClearBit(f_mask, pos); + ssize_t bits; + + if (pos_clear < 0) + pos_clear = virBitmapSize(f_mask); + + bits = pos_clear - pos; + + /* Not enough bits, move on and skip all of them */ + if (bits < need_bits) { + pos = pos_clear; + continue; + } + + /* This fits perfectly */ + if (bits == need_bits) { + last_pos = pos; + break; + } + + /* Remember the smaller region if we already found on before */ + if (last_pos < 0 || (last_bits && bits < last_bits)) { + last_bits = bits; + last_pos = pos; + } + + pos = pos_clear; + } + + if (last_pos < 0) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Not enough room for allocation of " + "%llu bytes for level %u cache %u " + "scope type '%s'"), + *size, level, cache, + virCacheTypeToString(type)); + goto cleanup; + } + + a_mask = virBitmapNew(i_type->bits); + for (i = last_pos; i < last_pos + need_bits; i++) { + ignore_value(virBitmapSetBit(a_mask, i)); + ignore_value(virBitmapClearBit(f_mask, i)); + } + + if (a_type->nmasks <= cache) { + if (VIR_EXPAND_N(a_type->masks, a_type->nmasks, + cache - a_type->nmasks + 1) < 0) { + virBitmapFree(a_mask); + goto cleanup; + } + } + a_type->masks[cache] = a_mask; + } + } + } + + ret = 0; + cleanup: + if (!save_ptr) + virObjectUnref(alloc_free); + return ret; +} + +/* This checks if the directory for the alloc exists. If not it tries to create + * it and apply appropriate alloc settings. */ +int +virResctrlAllocCreate(virResctrlInfoPtr r_info, + virResctrlAllocPtr alloc, + const char *drivername, + const char *machinename) +{ + char *alloc_path = NULL; + char *schemata_path = NULL; + bool dir_created = false; + char *alloc_str = NULL; + int ret = -1; + int lockfd = -1; + + if (!alloc) + return 0; + + if (!alloc->path && + virAsprintf(&alloc->path, "%s/%s-%s-%s", + SYSFS_RESCTRL_PATH, drivername, machinename, alloc->id) < 0)
This is being created in /sys/fs... and theoretically nothing will change for @drivername and @machinename
+ return -1; + + /* Check if this allocation was already created */ + if (virFileIsDir(alloc->path)) { + VIR_FREE(alloc_path);
dead code ;-) "alloc_path" is never allocated... Any concern over the guest being killed without running through virResctrlAllocRemove and the rmdir?
+ return 0; + } + + if (virFileExists(alloc->path)) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Path '%s' for resctrl allocation exists and is not a " + "directory"), alloc->path); + goto cleanup; + } + + lockfd = virResctrlLockWrite(); + if (lockfd < 0) + goto cleanup; + + if (virResctrlAllocMasksAssign(r_info, alloc, NULL) < 0) + goto cleanup; + + alloc_str = virResctrlAllocFormat(alloc); + if (!alloc_str) + return -1;
Leaking... and leaving 'em locked on the way out.
+ + if (virAsprintf(&schemata_path, "%s/schemata", alloc->path) < 0) + goto cleanup; + + if (virFileMakePath(alloc->path) < 0) { + virReportSystemError(errno, + _("Cannot create resctrl directory '%s'"), + alloc->path); + goto cleanup; + } + dir_created = true; + + if (virFileWriteStr(schemata_path, alloc_str, 0) < 0) { + virReportSystemError(errno, + _("Cannot write into schemata file '%s'"), + schemata_path); + goto cleanup; + } + + ret = 0; + cleanup: + if (ret < 0 && dir_created) + rmdir(alloc->path); + virResctrlUnlock(lockfd); + VIR_FREE(alloc_str); + VIR_FREE(alloc_path); + VIR_FREE(schemata_path); + return ret; +} + +int +virResctrlAllocAddPID(virResctrlAllocPtr alloc, + pid_t pid) +{ + char *tasks = NULL; + char *pidstr = NULL; + int ret = 0; + + if (!alloc->path) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Cannot add pid to non-existing resctrl allocation")); + return -1; + } + + if (virAsprintf(&tasks, "%s/tasks", alloc->path) < 0) + return -1; + + if (virAsprintf(&pidstr, "%lld", (long long int) pid) < 0) + goto cleanup; + + if (virFileWriteStr(tasks, pidstr, 0) < 0) { + virReportSystemError(errno, + _("Cannot write pid in tasks file '%s'"), + tasks); + goto cleanup; + } + + ret = 0; + cleanup: + VIR_FREE(tasks); + VIR_FREE(pidstr); + return ret; +} + +int +virResctrlAllocRemove(virResctrlAllocPtr alloc) +{ + int ret = 0; + + if (!alloc->path) + return 0; + + VIR_DEBUG("Removing resctrl allocation %s", alloc->path); + if (rmdir(alloc->path) != 0 && errno != ENOENT) { + ret = -errno; + VIR_ERROR(_("Unable to remove %s (%d)"), alloc->path, errno); + } + + return ret; +} diff --git a/src/util/virresctrl.h b/src/util/virresctrl.h index c4df88f23c3a..b233eca41c03 100644 --- a/src/util/virresctrl.h +++ b/src/util/virresctrl.h @@ -62,4 +62,63 @@ virResctrlInfoGetCache(virResctrlInfoPtr resctrl, size_t *ncontrols, virResctrlInfoPerCachePtr **controls);
+/* Alloc-related things */ +typedef struct _virResctrlAlloc virResctrlAlloc; +typedef virResctrlAlloc *virResctrlAllocPtr; + +typedef int virResctrlAllocForeachSizeCallback(unsigned int level, + virCacheType type, + unsigned int cache, + unsigned long long size, + void *opaque); + +virResctrlAllocPtr +virResctrlAllocNewFromInfo(virResctrlInfoPtr info); + +int +virResctrlAllocUpdateSize(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + unsigned long long size); + +int +virResctrlAllocUpdateMask(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + virBitmapPtr mask); + +int +virResctrlAllocSetSize(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + unsigned long long size); + +int +virResctrlAllocForeachSize(virResctrlAllocPtr resctrl, + virResctrlAllocForeachSizeCallback cb, + void *opaque); + +int +virResctrlAllocSetID(virResctrlAllocPtr alloc, + const char *id); + +char * +virResctrlAllocFormat(virResctrlAllocPtr alloc); + +int +virResctrlAllocCreate(virResctrlInfoPtr r_info, + virResctrlAllocPtr alloc, + const char *drivername, + const char *machinename); + +int +virResctrlAllocAddPID(virResctrlAllocPtr alloc, + pid_t pid); + +int +virResctrlAllocRemove(virResctrlAllocPtr alloc); + #endif /* __VIR_RESCTRL_H__ */ diff --git a/src/util/virresctrlpriv.h b/src/util/virresctrlpriv.h new file mode 100644 index 000000000000..4255ad496302 --- /dev/null +++ b/src/util/virresctrlpriv.h @@ -0,0 +1,32 @@ +/* + * virresctrlpriv.h: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + */ + +#ifndef __VIR_RESCTRL_PRIV_H__ +# define __VIR_RESCTRL_PRIV_H__ + +# include "virresctrl.h" + +virResctrlAllocPtr +virResctrlAllocGetFree(virResctrlInfoPtr resctrl); + +int +virResctrlAllocMasksAssign(virResctrlInfoPtr r_info, + virResctrlAllocPtr alloc, + void **save_ptr); + +#endif /* __VIR_RESCTRL_PRIV_H__ */

On Tue, Nov 14, 2017 at 06:52:48PM -0500, John Ferlan wrote:
On 11/13/2017 03:50 AM, Martin Kletzander wrote:
With this commit we finally have a way to read and manipulate basic resctrl settings. Locking is done only on exposed functions that read/write from/to resctrlfs. Not in fuctions that are exposed in virresctrlpriv.h as those are
functions
Fixed
only supposed to be used from tests.
Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- src/Makefile.am | 2 +- src/libvirt_private.syms | 12 + src/util/virresctrl.c | 1012 ++++++++++++++++++++++++++++++++++++++++++++- src/util/virresctrl.h | 59 +++ src/util/virresctrlpriv.h | 32 ++ 5 files changed, 1115 insertions(+), 2 deletions(-) create mode 100644 src/util/virresctrlpriv.h
This is a *lot* of code! I wasn't able to run through Coverity mainly because I have some stuff in a local branch that conflicts with earlier patches. If you push those, then I can apply these later patches and let Coverity have a peek on memory leaks or other strangeness that I could have missed below. I'll reserve the right to come back here again ;-) I think there's only a few "missed things".
I had this as one big gross ball of pus that I had to split into at least bit smaller chunks. I'm not sure I could've split this one even more. Maybe I could, but it didn't occur to me, mainly after renaming, rebasing and splitting if for non-trivial amount of time already. I'll push parts of it later, but I'm keeping this as a branch 'catwip' [1] on my github [2] where you can always get the code from in case it has conflicts with the newest master. [1] https://github.com/nertpinx/libvirt/tree/catwip [2] https://github.com/nertpinx/libvirt.git
diff --git a/src/Makefile.am b/src/Makefile.am index 1d24231249de..ad113262fbb0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -167,7 +167,7 @@ UTIL_SOURCES = \ util/virprocess.c util/virprocess.h \ util/virqemu.c util/virqemu.h \ util/virrandom.h util/virrandom.c \ - util/virresctrl.h util/virresctrl.c \ + util/virresctrl.h util/virresctrl.c util/virresctrlpriv.h \ util/virrotatingfile.h util/virrotatingfile.c \ util/virscsi.c util/virscsi.h \ util/virscsihost.c util/virscsihost.h \ diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index b24728ce4a1d..37bac41e618b 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2532,6 +2532,18 @@ virRandomInt; # util/virresctrl.h virCacheTypeFromString; virCacheTypeToString; +virResctrlAllocAddPID; +virResctrlAllocCreate; +virResctrlAllocForeachSize; +virResctrlAllocFormat; +virResctrlAllocGetFree; +virResctrlAllocMasksAssign; +virResctrlAllocNewFromInfo; +virResctrlAllocRemove; +virResctrlAllocSetID; +virResctrlAllocSetSize; +virResctrlAllocUpdateMask; +virResctrlAllocUpdateSize; virResctrlGetInfo; virResctrlInfoGetCache;
diff --git a/src/util/virresctrl.c b/src/util/virresctrl.c index 6c6692e78f42..ac1b38436bb2 100644 --- a/src/util/virresctrl.c +++ b/src/util/virresctrl.c @@ -23,7 +23,7 @@ #include <sys/stat.h> #include <fcntl.h>
-#include "virresctrl.h" +#include "virresctrlpriv.h"
#include "c-ctype.h" #include "count-one-bits.h" @@ -151,6 +151,153 @@ virResctrlInfoNew(void) }
+/* Alloc-related definitions and AllocClass-related functions */ +typedef struct _virResctrlAllocPerType virResctrlAllocPerType; +typedef virResctrlAllocPerType *virResctrlAllocPerTypePtr; +struct _virResctrlAllocPerType { + /* There could be bool saying whether this is set or not, but since everything + * in virResctrlAlloc (and most of libvirt) goes with pointer arrays we would + * have to have one more level of allocation anyway, so this stays faithful to + * the concept */ + unsigned long long **sizes; + size_t nsizes; + + /* Mask for each cache */ + virBitmapPtr *masks; + size_t nmasks; +}; + +typedef struct _virResctrlAllocPerLevel virResctrlAllocPerLevel; +typedef virResctrlAllocPerLevel *virResctrlAllocPerLevelPtr; +struct _virResctrlAllocPerLevel { + virResctrlAllocPerTypePtr *types; /* Indexed with enum virCacheType */ +}; + +struct _virResctrlAlloc { + virObject parent; + + virResctrlAllocPerLevelPtr *levels; + size_t nlevels; + + char *id; /* The identifier (any unique string for now) */ + char *path; +}; + +static virClassPtr virResctrlAllocClass; + +static void +virResctrlAllocDispose(void *obj) +{ + size_t i = 0; + size_t j = 0; + size_t k = 0; + + virResctrlAllocPtr resctrl = obj; + + for (i = 0; i < resctrl->nlevels; i++) { + virResctrlAllocPerLevelPtr level = resctrl->levels[--resctrl->nlevels];
Again the odd (to me at least) looking loop control that's reducing the for loop end condition.
Yeah, fixed now
+ + if (!level) + continue; + + for (j = 0; j < VIR_CACHE_TYPE_LAST; j++) { + virResctrlAllocPerTypePtr type = level->types[j]; + + if (!type) + continue; + + for (k = 0; k < type->nsizes; k++) + VIR_FREE(type->sizes[k]); + + VIR_FREE(type->sizes);
what about type->masks[k]
good point
You could create a Free function for each entry too.
could, maybe I will, but it should not be freed separately anyway...
+ VIR_FREE(type); + } + VIR_FREE(level->types); + VIR_FREE(level); + } + + VIR_FREE(resctrl->id);
resctrl->path ?
Yes! =)
+ VIR_FREE(resctrl->levels); +} +
Two blank lines (multiple instances in new functions)
I tried keeping this one more organized, two lines between groups of functions, one line between functions in the same group. But I can do two everywhere, the fact that I don't fully agree is irrelevant (unfortunately), but I'd rather get this in instead of arguing over the amount of whitespace =D
+static int virResctrlAllocOnceInit(void)
static int virResctrlAllocOnceInit(void)
Oh, missed this one.
+{ + if (!(virResctrlAllocClass = virClassNew(virClassForObject(), + "virResctrlAlloc", + sizeof(virResctrlAlloc), + virResctrlAllocDispose))) + return -1; + + return 0; +} + +VIR_ONCE_GLOBAL_INIT(virResctrlAlloc) + +static virResctrlAllocPtr +virResctrlAllocNew(void) +{ + if (virResctrlAllocInitialize() < 0) + return NULL; + + return virObjectNew(virResctrlAllocClass); +} + + +/* Common functions */ +static int +virResctrlLockInternal(int op) +{ + int fd = open(SYSFS_RESCTRL_PATH, O_DIRECTORY | O_CLOEXEC); + + if (fd < 0) { + virReportSystemError(errno, "%s", _("Cannot open resctrlfs")); + return -1; + } + + if (flock(fd, op) < 0) {
So only ever used on a local file system right? Linux file locking functions are confounding...
Yes, only local filesystem.
Why not use virFile{Lock|Unlock}?
Because that uses fcnlt(2) which is different lock which might not interactl with flock(2), so all programs working with resctrlfs must use the same type of lock. And resctrlfs should specifically use flock(2) according to the docs: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Docu...
+ virReportSystemError(errno, "%s", _("Cannot lock resctrlfs")); + VIR_FORCE_CLOSE(fd); + return -1; + } + + return fd; +} + +static inline int +virResctrlLockRead(void)
Not used in this series...
Yeah, this can be removed. Actually clang will complain about it. I'll remove it, it's not that difficult to add it later on :D
+{ + return virResctrlLockInternal(LOCK_SH); +} + +static inline int +virResctrlLockWrite(void) +{ + return virResctrlLockInternal(LOCK_EX); +} + +static int +virResctrlUnlock(int fd) +{ + int ret = -1; +
ret is pointless here.
+ if (fd == -1) + return 0; + + /* The lock gets unlocked by closing the fd, which we need to do anyway in + * order to clean up properly */ + if (VIR_CLOSE(fd) < 0) { + virReportSystemError(errno, "%s", _("Cannot close resctrlfs")); + + /* Trying to save the already broken */
So if close unlocks too, then why the unlock?
Only if the close failed, I figured we might as well try to safe the already broken, right? I can remove it if you want.
+ if (flock(fd, LOCK_UN) < 0) + virReportSystemError(errno, "%s", _("Cannot unlock resctrlfs")); + return -1; + } + + return ret; +} + + /* Info-related functions */ int virResctrlGetInfo(virResctrlInfoPtr *resctrl) @@ -318,3 +465,866 @@ virResctrlInfoGetCache(virResctrlInfoPtr resctrl, VIR_FREE(*controls); goto cleanup; } + + +/* Alloc-related functions */
A few notes about the arguments could be beneficial... Or at least the algorithm that allows partial allocations to work for future consumers.
+static virResctrlAllocPerTypePtr +virResctrlAllocFindType(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + bool alloc) +{ + virResctrlAllocPerLevelPtr a_level = NULL; + virResctrlAllocPtr tmp = NULL; + + if (!*resctrl) { + if (!alloc || !(*resctrl = virResctrlAllocNew())) + return NULL; + } + + tmp = *resctrl; + + /* Per-level array */ + if (tmp->nlevels <= level) { + if (!alloc || VIR_EXPAND_N(tmp->levels, tmp->nlevels, + level - tmp->nlevels + 1) < 0) + return NULL; + } + + if (!tmp->levels[level]) { + if (!alloc || + VIR_ALLOC(tmp->levels[level]) < 0 || + VIR_ALLOC_N(tmp->levels[level]->types, VIR_CACHE_TYPE_LAST) < 0) + return NULL; + } + a_level = tmp->levels[level]; + + if (!a_level->types[type]) { + if (!alloc || VIR_ALLOC(a_level->types[type]) < 0) + return NULL; + } + + return a_level->types[type]; +} + +static virBitmapPtr * +virResctrlAllocFindMask(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + bool alloc) +{ + virResctrlAllocPerTypePtr a_type = virResctrlAllocFindType(resctrl, level, + type, alloc); + + if (!a_type) + return NULL; + + if (!a_type->masks) { + if (!alloc || VIR_ALLOC_N(a_type->masks, cache + 1) < 0) + return NULL; + a_type->nmasks = cache + 1; + } else if (a_type->nmasks <= cache) { + if (!alloc || VIR_EXPAND_N(a_type->masks, a_type->nmasks, + cache - a_type->nmasks + 1) < 0) + return NULL; + } + + return a_type->masks + cache; +} + +static unsigned long long * +virResctrlAllocFindSize(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + bool alloc) +{ + virResctrlAllocPerTypePtr a_type = virResctrlAllocFindType(resctrl, level, + type, alloc); + + if (!a_type) + return NULL; + + if (!a_type->sizes) { + if (!alloc || VIR_ALLOC_N(a_type->sizes, cache + 1) < 0) + return NULL; + a_type->nsizes = cache + 1; + } else if (a_type->nsizes <= cache) { + if (!alloc || VIR_EXPAND_N(a_type->sizes, a_type->nsizes, + cache - a_type->nsizes + 1) < 0) + return NULL; + } + + if (!a_type->sizes[cache]) { + if (!alloc || VIR_ALLOC(a_type->sizes[cache]) < 0) + return NULL; + } + + return a_type->sizes[cache]; +} +
This could really use a functional description especially since it's external...
Interesting way to code this though - took a few attempts to stare at it before it finally started sinking in ;-)
+int +virResctrlAllocUpdateMask(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + virBitmapPtr mask) +{ + virBitmapPtr *found = virResctrlAllocFindMask(resctrl, level, type, cache, + true); + + if (!found) + return -1; + + virBitmapFree(*found); + + *found = virBitmapNew(virBitmapSize(mask)); + if (!*found) + return -1; + + return virBitmapCopy(*found, mask);> +} +
Similarly here too... I think "external" function should be commented. I'll only mention again here though...
+int +virResctrlAllocUpdateSize(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + unsigned long long size) +{ + unsigned long long *found = virResctrlAllocFindSize(resctrl, level, type, + cache, true); + + if (!found) + return -1; + + *found = size; + return 0; +} + +static bool +virResctrlAllocCheckCollision(virResctrlAllocPtr a, + unsigned int level, + virCacheType type, + unsigned int cache) +{ + /* If there is an allocation for type 'both', there can be no other + * allocation for the same cache */ + if (virResctrlAllocFindSize(&a, level, VIR_CACHE_TYPE_BOTH, cache, false)) + return true; + + if (type == VIR_CACHE_TYPE_BOTH) { + if (virResctrlAllocFindSize(&a, level, VIR_CACHE_TYPE_CODE, cache, false)) + return true; + if (virResctrlAllocFindSize(&a, level, VIR_CACHE_TYPE_DATA, cache, false)) + return true; + } + + /* And never two allocations for the same type */ + if (virResctrlAllocFindSize(&a, level, type, cache, false)) + return true; + + return false; +} + +int +virResctrlAllocSetSize(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + unsigned long long size) +{ + if (virResctrlAllocCheckCollision(*resctrl, level, type, cache)) { + virReportError(VIR_ERR_XML_ERROR, + _("Colliding cache allocations for cache " + "level '%u' id '%u', type '%s'"), + level, cache, virCacheTypeToString(type)); + return -1; + } + + return virResctrlAllocUpdateSize(resctrl, level, type, cache, size); +} + +int +virResctrlAllocForeachSize(virResctrlAllocPtr resctrl, + virResctrlAllocForeachSizeCallback cb, + void *opaque) +{ + int ret = 0; + unsigned int level = 0; + unsigned int type = 0; + unsigned int cache = 0; + + if (!resctrl) + return 0; + + for (level = 0; level < resctrl->nlevels; level++) { + virResctrlAllocPerLevelPtr a_level = resctrl->levels[level]; + + if (!a_level) + continue; + + for (type = 0; type < VIR_CACHE_TYPE_LAST; type++) { + virResctrlAllocPerTypePtr a_type = a_level->types[type]; + + if (!a_type) + continue; + + for (cache = 0; cache < a_type->nsizes; cache++) { + unsigned long long *size = a_type->sizes[cache]; + + if (!size) + continue; + + ret = cb(level, type, cache, *size, opaque); + if (ret < 0) + return ret; + } + } + } + + return 0; +} + +int +virResctrlAllocSetID(virResctrlAllocPtr alloc, + const char *id) +{ + if (!id) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Resctrl allocation id cannot be NULL")); + return -1; + } + + return VIR_STRDUP(alloc->id, id); +} + +char * +virResctrlAllocFormat(virResctrlAllocPtr resctrl)
No external consumer yet.
+{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + unsigned int level = 0; + unsigned int type = 0; + unsigned int cache = 0; + + if (!resctrl) + return NULL; + + for (level = 0; level < resctrl->nlevels; level++) { + virResctrlAllocPerLevelPtr a_level = resctrl->levels[level]; + + if (!a_level) + continue; + + for (type = 0; type < VIR_CACHE_TYPE_LAST; type++) { + virResctrlAllocPerTypePtr a_type = a_level->types[type]; + + if (!a_type) + continue; + + virBufferAsprintf(&buf, "L%u%s:", level, virResctrlTypeToString(type)); + + for (cache = 0; cache < a_type->nmasks; cache++) { + virBitmapPtr mask = a_type->masks[cache]; + char *mask_str = NULL; + + if (!mask) + continue; + + mask_str = virBitmapToString(mask, false, true); + if (!mask_str) { + virBufferFreeAndReset(&buf); + return NULL; + } + + virBufferAsprintf(&buf, "%u=%s;", cache, mask_str); + } + + virBufferTrim(&buf, ";", 1); + virBufferAddChar(&buf, '\n'); + } + } + + virBufferCheckError(&buf); + return virBufferContentAndReset(&buf); +} + +static int +virResctrlAllocParseProcessCache(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + char *cache) +{ + char *tmp = strchr(cache, '='); + unsigned int cache_id = 0; + virBitmapPtr mask = NULL; + int ret = -1; + + if (!tmp) + return 0; + + *tmp = '\0'; + tmp++; + + if (virStrToLong_uip(cache, NULL, 10, &cache_id) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Invalid cache id '%s'"), cache); + return -1; + } + + mask = virBitmapNewString(tmp); + if (!mask) + return -1; + + if (virResctrlAllocUpdateMask(resctrl, level, type, cache_id, mask) < 0) + goto cleanup; + + ret = 0; + cleanup: + virBitmapFree(mask); + return ret; +} + +static int +virResctrlAllocParseProcessLine(virResctrlAllocPtr *resctrl, + char *line) +{ + char **caches = NULL; + char *tmp = NULL; + unsigned int level = 0; + int type = -1; + size_t ncaches = 0; + size_t i = 0; + int ret = -1; + + /* Skip lines that don't concern caches, e.g. MB: etc. */ + if (line[0] != 'L') + return 0; + + /* And lines that we can't parse too */ + tmp = strchr(line, ':'); + if (!tmp) + return 0; + + *tmp = '\0'; + tmp++; + + if (virStrToLong_uip(line + 1, &line, 10, &level) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Cannot parse resctrl schema level '%s'"), + line + 1); + return -1; + } + + type = virResctrlTypeFromString(line); + if (type < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Cannot parse resctrl schema level '%s'"), + line + 1); + return -1; + } + + caches = virStringSplitCount(tmp, ";", 0, &ncaches); + if (!caches) + return 0; + + for (i = 0; i < ncaches; i++) { + if (virResctrlAllocParseProcessCache(resctrl, level, type, caches[i]) < 0) + goto cleanup; + } + + ret = 0; + cleanup: + virStringListFree(caches); + return ret; +} + +static int +virResctrlAllocParse(virResctrlAllocPtr *alloc, + const char *schemata) +{ + virResctrlAllocPtr tmp = NULL; + char **lines = NULL; + size_t nlines = 0; + size_t i = 0; + int ret = -1; + + lines = virStringSplitCount(schemata, "\n", 0, &nlines); + for (i = 0; i < nlines; i++) { + if (virResctrlAllocParseProcessLine(&tmp, lines[i]) < 0) + goto cleanup; + } + + *alloc = tmp; + tmp = NULL; + ret = 0; + cleanup: + virStringListFree(lines); + virObjectUnref(tmp); + return ret; +} + +static void +virResctrlAllocSubtractPerType(virResctrlAllocPerTypePtr a, + virResctrlAllocPerTypePtr b) +{ + size_t i = 0; + + if (!a || !b) + return; + + for (i = 0; i < a->nmasks && i < b->nmasks; ++i) { + if (a->masks[i] && b->masks[i]) + virBitmapSubtract(a->masks[i], b->masks[i]); + } +} + +static void +virResctrlAllocSubtract(virResctrlAllocPtr a, + virResctrlAllocPtr b) +{ + size_t i = 0; + size_t j = 0; + + if (!b) + return; + + for (i = 0; i < a->nlevels && b->nlevels; ++i) { + if (a->levels[i] && b->levels[i]) { + /* Here we rely on all the system allocations to use the same types. + * We kind of _hope_ it's the case. If this is left here until the + * review and someone finds it, then suggest only removing this last + * sentence. */
Should we use 'sa_assert' instead of just hoping? it's not the verboten assert, but at least the Coverity or Clang would catch, right?
It would not make any sense for it not to be true. I guess I was just angry with the Linux kernel interface for CAT when writing this. That was the case for most of the time I spent on this series. And some of the time I was not working at all. And sometimes when sleeping, too. Anyway, nothing bad happens if it's not true, maybe an allocation that is not purely exclusive will be created.
+ for (j = 0; j < VIR_CACHE_TYPE_LAST; j++) { + virResctrlAllocSubtractPerType(a->levels[i]->types[j], + b->levels[i]->types[j]); + } + } + } +} + +virResctrlAllocPtr +virResctrlAllocNewFromInfo(virResctrlInfoPtr info)
No external consumer
Good point, making it static.
+{ + size_t i = 0; + size_t j = 0; + size_t k = 0; + virResctrlAllocPtr ret = NULL; + virBitmapPtr mask = NULL; + + for (i = 0; i < info->nlevels; i++) { + virResctrlInfoPerLevelPtr i_level = info->levels[i]; + + if (!i_level) + continue; + + for (j = 0; j < VIR_CACHE_TYPE_LAST; j++) { + virResctrlInfoPerTypePtr i_type = i_level->types[j]; + + if (!i_type) + continue; + + virBitmapFree(mask); + mask = virBitmapNew(i_type->bits); + if (!mask) + goto error; + virBitmapSetAll(mask); + + for (k = 0; k <= i_type->max_cache_id; k++) { + if (virResctrlAllocUpdateMask(&ret, i, j, k, mask) < 0) + goto error; + } + } + } + + cleanup: + virBitmapFree(mask); + return ret; + error: + virObjectUnref(ret); + ret = NULL; + goto cleanup; +} + +virResctrlAllocPtr +virResctrlAllocGetFree(virResctrlInfoPtr resctrl)
No external consumer...
Actually, there will be one, in tests, but there is circular dependency with the other function (AllocMasksAssign). The tests need both XML support and this file, XML support needs this file as well, but if we say this file needs tests then it's circular. And I wanted to split it a little bit so we don't get that huge files.
+{ + virResctrlAllocPtr ret = NULL; + virResctrlAllocPtr alloc = NULL; + virBitmapPtr mask = NULL; + struct dirent *ent = NULL; + DIR *dirp = NULL; + char *schemata = NULL; + int rv = -1; + + ret = virResctrlAllocNewFromInfo(resctrl); + if (!ret) + return NULL; + + if (virFileReadValueString(&schemata, + SYSFS_RESCTRL_PATH + "/schemata") < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Could not read schemata file for the default group")); + goto error; + } + + if (virResctrlAllocParse(&alloc, schemata) < 0) + goto error; + if (!alloc) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("No schemata for default resctrlfs group")); + goto error; + } + virResctrlAllocSubtract(ret, alloc); + + if (virDirOpen(&dirp, SYSFS_RESCTRL_PATH) < 0) + goto error; + + while ((rv = virDirRead(dirp, &ent, SYSFS_RESCTRL_PATH)) > 0) { + if (ent->d_type != DT_DIR) + continue; + + if (STREQ(ent->d_name, "info")) + continue; + + VIR_FREE(schemata); + if (virFileReadValueString(&schemata, + SYSFS_RESCTRL_PATH + "/%s/schemata", + ent->d_name) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Could not read schemata file for group %s"), + ent->d_name); + goto error; + } + + virObjectUnref(alloc); + alloc = NULL; + if (virResctrlAllocParse(&alloc, schemata) < 0) + goto error; + + virResctrlAllocSubtract(ret, alloc); + + VIR_FREE(schemata); + } + if (rv < 0) + goto error; + + cleanup: + virObjectUnref(alloc); + VIR_DIR_CLOSE(dirp); + VIR_FREE(schemata); + virBitmapFree(mask); + return ret; + + error: + virObjectUnref(ret); + ret = NULL; + goto cleanup; +} + +int +virResctrlAllocMasksAssign(virResctrlInfoPtr r_info, + virResctrlAllocPtr alloc, + void **save_ptr)
No external consumer...
see above
+{ + int ret = -1; + unsigned int level = 0; + virResctrlAllocPtr alloc_free = NULL; + + if (!r_info) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("Resource control is not supported on this host")); + return -1; + } + + if (!save_ptr) { + alloc_free = virResctrlAllocGetFree(r_info); + } else { + if (!*save_ptr) + *save_ptr = virResctrlAllocGetFree(r_info); + + alloc_free = *save_ptr; + } + + if (!alloc_free) + return -1; + + for (level = 0; level < alloc->nlevels; level++) { + virResctrlAllocPerLevelPtr a_level = alloc->levels[level]; + virResctrlAllocPerLevelPtr f_level = NULL; + unsigned int type = 0; + + if (!a_level) + continue; + + if (level < alloc_free->nlevels) + f_level = alloc_free->levels[level]; + + if (!f_level) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cache level %d does not support tuning"), + level); + goto cleanup; + } + + for (type = 0; type < VIR_CACHE_TYPE_LAST; type++) {
OMG, my eyes need a beer!
did someone say "beer"?
+ virResctrlAllocPerTypePtr a_type = a_level->types[type]; + virResctrlAllocPerTypePtr f_type = f_level->types[type]; + unsigned int cache = 0; + + if (!a_type) + continue; + + if (!f_type) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cache level %d does not support tuning for " + "scope type '%s'"), + level, virCacheTypeToString(type)); + goto cleanup; + } + + for (cache = 0; cache < a_type->nsizes; cache++) { + unsigned long long *size = a_type->sizes[cache]; + virBitmapPtr a_mask = NULL; + virBitmapPtr f_mask = f_type->masks[cache]; + virResctrlInfoPerLevelPtr i_level = r_info->levels[level]; + virResctrlInfoPerTypePtr i_type = i_level->types[type]; + unsigned long long granularity; + unsigned long long need_bits; + size_t i = 0; + ssize_t pos = -1; + ssize_t last_bits = 0; + ssize_t last_pos = -1; + + if (!size) + continue; + + if (cache >= f_type->nmasks) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cache with id %u does not exists for level %d"), + cache, level); + goto cleanup; + } + + f_mask = f_type->masks[cache]; + if (!f_mask) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cache level %d id %u does not support tuning for " + "scope type '%s'"), + level, cache, virCacheTypeToString(type)); + goto cleanup; + } + + granularity = i_type->size / i_type->bits; + need_bits = *size / granularity; + + if (*size % granularity) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cache allocation of size %llu is not " + "divisible by granularity %llu"), + *size, granularity); + goto cleanup; + } + + if (need_bits < i_type->min_cbm_bits) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cache allocation of size %llu is smaller " + "than the minimum allowed allocation %llu"), + *size, granularity * i_type->min_cbm_bits); + goto cleanup; + } + + while ((pos = virBitmapNextSetBit(f_mask, pos)) >= 0) { + ssize_t pos_clear = virBitmapNextClearBit(f_mask, pos); + ssize_t bits; + + if (pos_clear < 0) + pos_clear = virBitmapSize(f_mask); + + bits = pos_clear - pos; + + /* Not enough bits, move on and skip all of them */ + if (bits < need_bits) { + pos = pos_clear; + continue; + } + + /* This fits perfectly */ + if (bits == need_bits) { + last_pos = pos; + break; + } + + /* Remember the smaller region if we already found on before */ + if (last_pos < 0 || (last_bits && bits < last_bits)) { + last_bits = bits; + last_pos = pos; + } + + pos = pos_clear; + } + + if (last_pos < 0) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Not enough room for allocation of " + "%llu bytes for level %u cache %u " + "scope type '%s'"), + *size, level, cache, + virCacheTypeToString(type)); + goto cleanup; + } + + a_mask = virBitmapNew(i_type->bits); + for (i = last_pos; i < last_pos + need_bits; i++) { + ignore_value(virBitmapSetBit(a_mask, i)); + ignore_value(virBitmapClearBit(f_mask, i)); + } + + if (a_type->nmasks <= cache) { + if (VIR_EXPAND_N(a_type->masks, a_type->nmasks, + cache - a_type->nmasks + 1) < 0) { + virBitmapFree(a_mask); + goto cleanup; + } + } + a_type->masks[cache] = a_mask; + } + } + } + + ret = 0; + cleanup: + if (!save_ptr) + virObjectUnref(alloc_free); + return ret; +} + +/* This checks if the directory for the alloc exists. If not it tries to create + * it and apply appropriate alloc settings. */ +int +virResctrlAllocCreate(virResctrlInfoPtr r_info, + virResctrlAllocPtr alloc, + const char *drivername, + const char *machinename) +{ + char *alloc_path = NULL; + char *schemata_path = NULL; + bool dir_created = false; + char *alloc_str = NULL; + int ret = -1; + int lockfd = -1; + + if (!alloc) + return 0; + + if (!alloc->path && + virAsprintf(&alloc->path, "%s/%s-%s-%s", + SYSFS_RESCTRL_PATH, drivername, machinename, alloc->id) < 0)
This is being created in /sys/fs... and theoretically nothing will change for @drivername and @machinename
+ return -1; + + /* Check if this allocation was already created */ + if (virFileIsDir(alloc->path)) { + VIR_FREE(alloc_path);
dead code ;-) "alloc_path" is never allocated...
s/alloc_path/alloc->path/ =)
Any concern over the guest being killed without running through virResctrlAllocRemove and the rmdir?
Yes, where do you see that? The remove will be done in qemuProcessStop(). I can remove this one puny directory here, but proper cleanup needs to be done for all anyway.
+ return 0; + } + + if (virFileExists(alloc->path)) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Path '%s' for resctrl allocation exists and is not a " + "directory"), alloc->path); + goto cleanup; + } + + lockfd = virResctrlLockWrite(); + if (lockfd < 0) + goto cleanup; + + if (virResctrlAllocMasksAssign(r_info, alloc, NULL) < 0) + goto cleanup; + + alloc_str = virResctrlAllocFormat(alloc); + if (!alloc_str) + return -1;
Leaking... and leaving 'em locked on the way out.
good point. Peter-style bug, I'll fix this. I'll rebase all of these with the fixes and push it on the github so that it's easier to work with. Thanks for the review.

[...]
Two blank lines (multiple instances in new functions)
I tried keeping this one more organized, two lines between groups of functions, one line between functions in the same group. But I can do two everywhere, the fact that I don't fully agree is irrelevant (unfortunately), but I'd rather get this in instead of arguing over the amount of whitespace =D
I guess I'm just going by other reviews I've done and received where the 2 lines was requested for anything "new"... Not everyone follows it and I'm sure I've missed a few along the way.
+static int virResctrlAllocOnceInit(void)
static int virResctrlAllocOnceInit(void)
Oh, missed this one.
+{ + if (!(virResctrlAllocClass = virClassNew(virClassForObject(), + "virResctrlAlloc", + sizeof(virResctrlAlloc), + virResctrlAllocDispose))) + return -1; + + return 0; +} + +VIR_ONCE_GLOBAL_INIT(virResctrlAlloc) + +static virResctrlAllocPtr +virResctrlAllocNew(void) +{ + if (virResctrlAllocInitialize() < 0) + return NULL; + + return virObjectNew(virResctrlAllocClass); +} + + +/* Common functions */ +static int +virResctrlLockInternal(int op) +{ + int fd = open(SYSFS_RESCTRL_PATH, O_DIRECTORY | O_CLOEXEC); + + if (fd < 0) { + virReportSystemError(errno, "%s", _("Cannot open resctrlfs")); + return -1; + } + + if (flock(fd, op) < 0) {
So only ever used on a local file system right? Linux file locking functions are confounding...
Yes, only local filesystem.
Why not use virFile{Lock|Unlock}?
Because that uses fcnlt(2) which is different lock which might not interactl with flock(2), so all programs working with resctrlfs must use the same type of lock. And resctrlfs should specifically use flock(2) according to the docs:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Docu...
[...]
+ if (fd == -1) + return 0; + + /* The lock gets unlocked by closing the fd, which we need to do anyway in + * order to clean up properly */ + if (VIR_CLOSE(fd) < 0) { + virReportSystemError(errno, "%s", _("Cannot close resctrlfs")); + + /* Trying to save the already broken */
So if close unlocks too, then why the unlock?
Only if the close failed, I figured we might as well try to safe the already broken, right? I can remove it if you want.
oh right - no, it's fine here. "Eye" think I missed the on failure part!
+ if (flock(fd, LOCK_UN) < 0) + virReportSystemError(errno, "%s", _("Cannot unlock resctrlfs")); + return -1; + } + + return ret; +} + +
[...]
+/* This checks if the directory for the alloc exists. If not it tries to create + * it and apply appropriate alloc settings. */ +int +virResctrlAllocCreate(virResctrlInfoPtr r_info, + virResctrlAllocPtr alloc, + const char *drivername, + const char *machinename) +{ + char *alloc_path = NULL; + char *schemata_path = NULL; + bool dir_created = false; + char *alloc_str = NULL; + int ret = -1; + int lockfd = -1; + + if (!alloc) + return 0; + + if (!alloc->path && + virAsprintf(&alloc->path, "%s/%s-%s-%s", + SYSFS_RESCTRL_PATH, drivername, machinename, alloc->id) < 0)
This is being created in /sys/fs... and theoretically nothing will change for @drivername and @machinename
+ return -1; + + /* Check if this allocation was already created */ + if (virFileIsDir(alloc->path)) { + VIR_FREE(alloc_path);
dead code ;-) "alloc_path" is never allocated...
s/alloc_path/alloc->path/ =)
Any concern over the guest being killed without running through virResctrlAllocRemove and the rmdir?
Yes, where do you see that? The remove will be done in qemuProcessStop(). I can remove this one puny directory here, but proper cleanup needs to be done for all anyway.
No where in particular - there's so much code to wade through and attempt to remember. I do see the call for Stop - just trying to think if there was some other way for guest death that could cause problems. The only other thought I had along the lines here was writing into /sys/fs - not just the privilege but the size/number of files being created in /sys, but perhaps other things distracted those (beer?) thoughts... I was going to ask if output such as this should be something like ->libDir which could be deleted "for free"... If not should the rmdir(alloc->path) be a virDeleteTree(alloc->path) since you're creating "schemata" and "tasks" John [...]

On Wed, Nov 15, 2017 at 01:17:56PM -0500, John Ferlan wrote:
[...]
Two blank lines (multiple instances in new functions)
I tried keeping this one more organized, two lines between groups of functions, one line between functions in the same group. But I can do two everywhere, the fact that I don't fully agree is irrelevant (unfortunately), but I'd rather get this in instead of arguing over the amount of whitespace =D
I guess I'm just going by other reviews I've done and received where the 2 lines was requested for anything "new"... Not everyone follows it and I'm sure I've missed a few along the way.
I may miss some when amending the commits as well, even though I'm going line by line and adjusting it as I go. I'll just put 2 everywhere.
+static int virResctrlAllocOnceInit(void)
static int virResctrlAllocOnceInit(void)
Oh, missed this one.
+{ + if (!(virResctrlAllocClass = virClassNew(virClassForObject(), + "virResctrlAlloc", + sizeof(virResctrlAlloc), + virResctrlAllocDispose))) + return -1; + + return 0; +} + +VIR_ONCE_GLOBAL_INIT(virResctrlAlloc) + +static virResctrlAllocPtr +virResctrlAllocNew(void) +{ + if (virResctrlAllocInitialize() < 0) + return NULL; + + return virObjectNew(virResctrlAllocClass); +} + + +/* Common functions */ +static int +virResctrlLockInternal(int op) +{ + int fd = open(SYSFS_RESCTRL_PATH, O_DIRECTORY | O_CLOEXEC); + + if (fd < 0) { + virReportSystemError(errno, "%s", _("Cannot open resctrlfs")); + return -1; + } + + if (flock(fd, op) < 0) {
So only ever used on a local file system right? Linux file locking functions are confounding...
Yes, only local filesystem.
Why not use virFile{Lock|Unlock}?
Because that uses fcnlt(2) which is different lock which might not interactl with flock(2), so all programs working with resctrlfs must use the same type of lock. And resctrlfs should specifically use flock(2) according to the docs:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Docu...
[...]
+ if (fd == -1) + return 0; + + /* The lock gets unlocked by closing the fd, which we need to do anyway in + * order to clean up properly */ + if (VIR_CLOSE(fd) < 0) { + virReportSystemError(errno, "%s", _("Cannot close resctrlfs")); + + /* Trying to save the already broken */
So if close unlocks too, then why the unlock?
Only if the close failed, I figured we might as well try to safe the already broken, right? I can remove it if you want.
oh right - no, it's fine here. "Eye" think I missed the on failure part!
+ if (flock(fd, LOCK_UN) < 0) + virReportSystemError(errno, "%s", _("Cannot unlock resctrlfs")); + return -1; + } + + return ret; +} + +
[...]
+/* This checks if the directory for the alloc exists. If not it tries to create + * it and apply appropriate alloc settings. */ +int +virResctrlAllocCreate(virResctrlInfoPtr r_info, + virResctrlAllocPtr alloc, + const char *drivername, + const char *machinename) +{ + char *alloc_path = NULL; + char *schemata_path = NULL; + bool dir_created = false; + char *alloc_str = NULL; + int ret = -1; + int lockfd = -1; + + if (!alloc) + return 0; + + if (!alloc->path && + virAsprintf(&alloc->path, "%s/%s-%s-%s", + SYSFS_RESCTRL_PATH, drivername, machinename, alloc->id) < 0)
This is being created in /sys/fs... and theoretically nothing will change for @drivername and @machinename
+ return -1; + + /* Check if this allocation was already created */ + if (virFileIsDir(alloc->path)) { + VIR_FREE(alloc_path);
dead code ;-) "alloc_path" is never allocated...
s/alloc_path/alloc->path/ =)
Any concern over the guest being killed without running through virResctrlAllocRemove and the rmdir?
Yes, where do you see that? The remove will be done in qemuProcessStop(). I can remove this one puny directory here, but proper cleanup needs to be done for all anyway.
No where in particular - there's so much code to wade through and attempt to remember. I do see the call for Stop - just trying to think if there was some other way for guest death that could cause problems.
in qemu the ProcessStop is called always precisely for the reason so that we have one place to clean up stuff.
The only other thought I had along the lines here was writing into /sys/fs - not just the privilege but the size/number of files being
I should check that the daemon is running as privileged.
created in /sys, but perhaps other things distracted those (beer?) thoughts... I was going to ask if output such as this should be something like ->libDir which could be deleted "for free"...
It's not a normal filesystem, it behaves similarly to cgroups and it's theonly kernel interface for this. I cannot create the files where I please.
If not should the rmdir(alloc->path) be a virDeleteTree(alloc->path) since you're creating "schemata" and "tasks"
I'm not, when you create the directory the files are "just there", like cgroup dirs. You cannot virFileDeleteTree because you'll get error on removing any file.
John
[...]

On Mon, Nov 13, 2017 at 09:50:31AM +0100, Martin Kletzander wrote:
With this commit we finally have a way to read and manipulate basic resctrl settings. Locking is done only on exposed functions that read/write from/to resctrlfs. Not in fuctions that are exposed in virresctrlpriv.h as those are only supposed to be used from tests.
Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- src/Makefile.am | 2 +- src/libvirt_private.syms | 12 + src/util/virresctrl.c | 1012 ++++++++++++++++++++++++++++++++++++++++++++- src/util/virresctrl.h | 59 +++ src/util/virresctrlpriv.h | 32 ++ 5 files changed, 1115 insertions(+), 2 deletions(-) create mode 100644 src/util/virresctrlpriv.h
diff --git a/src/Makefile.am b/src/Makefile.am index 1d24231249de..ad113262fbb0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -167,7 +167,7 @@ UTIL_SOURCES = \ util/virprocess.c util/virprocess.h \ util/virqemu.c util/virqemu.h \ util/virrandom.h util/virrandom.c \ - util/virresctrl.h util/virresctrl.c \ + util/virresctrl.h util/virresctrl.c util/virresctrlpriv.h \
Use only single space instead of tab after "util/virresctrl.c" and "util/virresctrlpriv.h".
util/virrotatingfile.h util/virrotatingfile.c \ util/virscsi.c util/virscsi.h \ util/virscsihost.c util/virscsihost.h \ diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index b24728ce4a1d..37bac41e618b 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2532,6 +2532,18 @@ virRandomInt; # util/virresctrl.h virCacheTypeFromString; virCacheTypeToString; +virResctrlAllocAddPID; +virResctrlAllocCreate; +virResctrlAllocForeachSize; +virResctrlAllocFormat; +virResctrlAllocGetFree; +virResctrlAllocMasksAssign; +virResctrlAllocNewFromInfo; +virResctrlAllocRemove; +virResctrlAllocSetID; +virResctrlAllocSetSize; +virResctrlAllocUpdateMask; +virResctrlAllocUpdateSize; virResctrlGetInfo; virResctrlInfoGetCache;
diff --git a/src/util/virresctrl.c b/src/util/virresctrl.c index 6c6692e78f42..ac1b38436bb2 100644 --- a/src/util/virresctrl.c +++ b/src/util/virresctrl.c @@ -23,7 +23,7 @@ #include <sys/stat.h> #include <fcntl.h>
-#include "virresctrl.h" +#include "virresctrlpriv.h"
#include "c-ctype.h" #include "count-one-bits.h" @@ -151,6 +151,153 @@ virResctrlInfoNew(void) }
+/* Alloc-related definitions and AllocClass-related functions */ +typedef struct _virResctrlAllocPerType virResctrlAllocPerType; +typedef virResctrlAllocPerType *virResctrlAllocPerTypePtr; +struct _virResctrlAllocPerType { + /* There could be bool saying whether this is set or not, but since everything + * in virResctrlAlloc (and most of libvirt) goes with pointer arrays we would + * have to have one more level of allocation anyway, so this stays faithful to + * the concept */ + unsigned long long **sizes; + size_t nsizes; + + /* Mask for each cache */ + virBitmapPtr *masks; + size_t nmasks; +}; + +typedef struct _virResctrlAllocPerLevel virResctrlAllocPerLevel; +typedef virResctrlAllocPerLevel *virResctrlAllocPerLevelPtr; +struct _virResctrlAllocPerLevel { + virResctrlAllocPerTypePtr *types; /* Indexed with enum virCacheType */ +}; + +struct _virResctrlAlloc { + virObject parent; + + virResctrlAllocPerLevelPtr *levels; + size_t nlevels; + + char *id; /* The identifier (any unique string for now) */ + char *path; +}; + +static virClassPtr virResctrlAllocClass; + +static void +virResctrlAllocDispose(void *obj) +{ + size_t i = 0; + size_t j = 0; + size_t k = 0; + + virResctrlAllocPtr resctrl = obj; + + for (i = 0; i < resctrl->nlevels; i++) { + virResctrlAllocPerLevelPtr level = resctrl->levels[--resctrl->nlevels]; + + if (!level) + continue; + + for (j = 0; j < VIR_CACHE_TYPE_LAST; j++) { + virResctrlAllocPerTypePtr type = level->types[j]; + + if (!type) + continue; + + for (k = 0; k < type->nsizes; k++) + VIR_FREE(type->sizes[k]); + + VIR_FREE(type->sizes); + VIR_FREE(type); + } + VIR_FREE(level->types); + VIR_FREE(level); + } + + VIR_FREE(resctrl->id); + VIR_FREE(resctrl->levels); +} + +static int virResctrlAllocOnceInit(void) +{ + if (!(virResctrlAllocClass = virClassNew(virClassForObject(), + "virResctrlAlloc", + sizeof(virResctrlAlloc), + virResctrlAllocDispose))) + return -1; + + return 0; +} + +VIR_ONCE_GLOBAL_INIT(virResctrlAlloc) + +static virResctrlAllocPtr +virResctrlAllocNew(void) +{ + if (virResctrlAllocInitialize() < 0) + return NULL; + + return virObjectNew(virResctrlAllocClass); +} + + +/* Common functions */ +static int +virResctrlLockInternal(int op) +{ + int fd = open(SYSFS_RESCTRL_PATH, O_DIRECTORY | O_CLOEXEC); + + if (fd < 0) { + virReportSystemError(errno, "%s", _("Cannot open resctrlfs")); + return -1; + } + + if (flock(fd, op) < 0) { + virReportSystemError(errno, "%s", _("Cannot lock resctrlfs")); + VIR_FORCE_CLOSE(fd); + return -1; + } + + return fd; +} + +static inline int +virResctrlLockRead(void) +{ + return virResctrlLockInternal(LOCK_SH); +} + +static inline int +virResctrlLockWrite(void) +{ + return virResctrlLockInternal(LOCK_EX); +} + +static int +virResctrlUnlock(int fd) +{ + int ret = -1; + + if (fd == -1) + return 0; + + /* The lock gets unlocked by closing the fd, which we need to do anyway in + * order to clean up properly */ + if (VIR_CLOSE(fd) < 0) { + virReportSystemError(errno, "%s", _("Cannot close resctrlfs")); + + /* Trying to save the already broken */ + if (flock(fd, LOCK_UN) < 0) + virReportSystemError(errno, "%s", _("Cannot unlock resctrlfs")); + return -1; + } + + return ret; +} + + /* Info-related functions */ int virResctrlGetInfo(virResctrlInfoPtr *resctrl) @@ -318,3 +465,866 @@ virResctrlInfoGetCache(virResctrlInfoPtr resctrl, VIR_FREE(*controls); goto cleanup; } + + +/* Alloc-related functions */ +static virResctrlAllocPerTypePtr +virResctrlAllocFindType(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + bool alloc) +{
I don't like this implementation, it's too complex and it does two different things based on a bool attribute. I see the benefit that it's convenient but IMHO it's ugly. The only call path that doesn't need allocation is from virResctrlAllocCheckCollision(). The remaining two calls virResctrlAllocUpdateMask() and virResctrlAllocUpdateSize() needs to allocate the internal structures of *virResctrlAllocPtr* object. Another point is that there is no need to have this function create new *virResctrlAllocPtr* object on demand, I would prefer creating that object in advance before we even start filling all the data.
+ virResctrlAllocPerLevelPtr a_level = NULL; + virResctrlAllocPtr tmp = NULL; + + if (!*resctrl) { + if (!alloc || !(*resctrl = virResctrlAllocNew())) + return NULL; + } + + tmp = *resctrl; + + /* Per-level array */ + if (tmp->nlevels <= level) { + if (!alloc || VIR_EXPAND_N(tmp->levels, tmp->nlevels, + level - tmp->nlevels + 1) < 0) + return NULL; + } + + if (!tmp->levels[level]) { + if (!alloc || + VIR_ALLOC(tmp->levels[level]) < 0 || + VIR_ALLOC_N(tmp->levels[level]->types, VIR_CACHE_TYPE_LAST) < 0) + return NULL; + } + a_level = tmp->levels[level]; + + if (!a_level->types[type]) { + if (!alloc || VIR_ALLOC(a_level->types[type]) < 0) + return NULL; + } + + return a_level->types[type]; +} + +static virBitmapPtr * +virResctrlAllocFindMask(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + bool alloc) +{
The code of this function can be merged into virResctrlAllocUpdateMask() and again only allocate the structures and set the mask. Currently there is no need for "Find" function if we will need it in the future it should definitely only find the mask, not allocate it.
+ virResctrlAllocPerTypePtr a_type = virResctrlAllocFindType(resctrl, level, + type, alloc); + + if (!a_type) + return NULL; + + if (!a_type->masks) { + if (!alloc || VIR_ALLOC_N(a_type->masks, cache + 1) < 0) + return NULL; + a_type->nmasks = cache + 1; + } else if (a_type->nmasks <= cache) { + if (!alloc || VIR_EXPAND_N(a_type->masks, a_type->nmasks, + cache - a_type->nmasks + 1) < 0) + return NULL; + } + + return a_type->masks + cache; +} + +static unsigned long long * +virResctrlAllocFindSize(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + bool alloc) +{ + virResctrlAllocPerTypePtr a_type = virResctrlAllocFindType(resctrl, level, + type, alloc);
Same as for virResctrlAllocFindMask(). With the exception that we actually need lookup function so create a one, that will only check whether there is some size set or not.
+ + if (!a_type) + return NULL; + + if (!a_type->sizes) { + if (!alloc || VIR_ALLOC_N(a_type->sizes, cache + 1) < 0) + return NULL; + a_type->nsizes = cache + 1; + } else if (a_type->nsizes <= cache) { + if (!alloc || VIR_EXPAND_N(a_type->sizes, a_type->nsizes, + cache - a_type->nsizes + 1) < 0) + return NULL; + } + + if (!a_type->sizes[cache]) { + if (!alloc || VIR_ALLOC(a_type->sizes[cache]) < 0) + return NULL; + } + + return a_type->sizes[cache]; +} + +int +virResctrlAllocUpdateMask(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + virBitmapPtr mask) +{ + virBitmapPtr *found = virResctrlAllocFindMask(resctrl, level, type, cache, + true); + + if (!found) + return -1; + + virBitmapFree(*found); + + *found = virBitmapNew(virBitmapSize(mask)); + if (!*found) + return -1; + + return virBitmapCopy(*found, mask); +} + +int +virResctrlAllocUpdateSize(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + unsigned long long size) +{ + unsigned long long *found = virResctrlAllocFindSize(resctrl, level, type, + cache, true); + + if (!found) + return -1; + + *found = size; + return 0; +} + +static bool +virResctrlAllocCheckCollision(virResctrlAllocPtr a, + unsigned int level, + virCacheType type, + unsigned int cache) +{ + /* If there is an allocation for type 'both', there can be no other + * allocation for the same cache */ + if (virResctrlAllocFindSize(&a, level, VIR_CACHE_TYPE_BOTH, cache, false)) + return true; + + if (type == VIR_CACHE_TYPE_BOTH) { + if (virResctrlAllocFindSize(&a, level, VIR_CACHE_TYPE_CODE, cache, false)) + return true; + if (virResctrlAllocFindSize(&a, level, VIR_CACHE_TYPE_DATA, cache, false)) + return true; + } + + /* And never two allocations for the same type */ + if (virResctrlAllocFindSize(&a, level, type, cache, false)) + return true; + + return false; +} + +int +virResctrlAllocSetSize(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + unsigned long long size) +{ + if (virResctrlAllocCheckCollision(*resctrl, level, type, cache)) { + virReportError(VIR_ERR_XML_ERROR, + _("Colliding cache allocations for cache " + "level '%u' id '%u', type '%s'"), + level, cache, virCacheTypeToString(type)); + return -1; + } + + return virResctrlAllocUpdateSize(resctrl, level, type, cache, size); +} + +int +virResctrlAllocForeachSize(virResctrlAllocPtr resctrl, + virResctrlAllocForeachSizeCallback cb, + void *opaque) +{ + int ret = 0; + unsigned int level = 0; + unsigned int type = 0; + unsigned int cache = 0; + + if (!resctrl) + return 0; + + for (level = 0; level < resctrl->nlevels; level++) { + virResctrlAllocPerLevelPtr a_level = resctrl->levels[level]; + + if (!a_level) + continue; + + for (type = 0; type < VIR_CACHE_TYPE_LAST; type++) { + virResctrlAllocPerTypePtr a_type = a_level->types[type]; + + if (!a_type) + continue; + + for (cache = 0; cache < a_type->nsizes; cache++) { + unsigned long long *size = a_type->sizes[cache]; + + if (!size) + continue; + + ret = cb(level, type, cache, *size, opaque); + if (ret < 0) + return ret; + } + } + } + + return 0; +} + +int +virResctrlAllocSetID(virResctrlAllocPtr alloc, + const char *id) +{ + if (!id) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Resctrl allocation id cannot be NULL")); + return -1; + } + + return VIR_STRDUP(alloc->id, id); +}
This is how I would expect all the other public functions to look like. Simple, does one thing and there is no magic.
+ +char * +virResctrlAllocFormat(virResctrlAllocPtr resctrl) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + unsigned int level = 0; + unsigned int type = 0; + unsigned int cache = 0; + + if (!resctrl) + return NULL; + + for (level = 0; level < resctrl->nlevels; level++) { + virResctrlAllocPerLevelPtr a_level = resctrl->levels[level]; + + if (!a_level) + continue; + + for (type = 0; type < VIR_CACHE_TYPE_LAST; type++) { + virResctrlAllocPerTypePtr a_type = a_level->types[type]; + + if (!a_type) + continue; + + virBufferAsprintf(&buf, "L%u%s:", level, virResctrlTypeToString(type)); + + for (cache = 0; cache < a_type->nmasks; cache++) { + virBitmapPtr mask = a_type->masks[cache]; + char *mask_str = NULL; + + if (!mask) + continue; + + mask_str = virBitmapToString(mask, false, true); + if (!mask_str) { + virBufferFreeAndReset(&buf); + return NULL; + } + + virBufferAsprintf(&buf, "%u=%s;", cache, mask_str); + } + + virBufferTrim(&buf, ";", 1); + virBufferAddChar(&buf, '\n'); + } + } + + virBufferCheckError(&buf); + return virBufferContentAndReset(&buf); +} + +static int +virResctrlAllocParseProcessCache(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + char *cache) +{ + char *tmp = strchr(cache, '='); + unsigned int cache_id = 0; + virBitmapPtr mask = NULL; + int ret = -1; + + if (!tmp) + return 0; + + *tmp = '\0'; + tmp++; + + if (virStrToLong_uip(cache, NULL, 10, &cache_id) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Invalid cache id '%s'"), cache); + return -1; + } + + mask = virBitmapNewString(tmp); + if (!mask) + return -1; + + if (virResctrlAllocUpdateMask(resctrl, level, type, cache_id, mask) < 0) + goto cleanup; + + ret = 0; + cleanup: + virBitmapFree(mask); + return ret; +} + +static int +virResctrlAllocParseProcessLine(virResctrlAllocPtr *resctrl, + char *line) +{ + char **caches = NULL; + char *tmp = NULL; + unsigned int level = 0; + int type = -1; + size_t ncaches = 0; + size_t i = 0; + int ret = -1; + + /* Skip lines that don't concern caches, e.g. MB: etc. */ + if (line[0] != 'L') + return 0; + + /* And lines that we can't parse too */ + tmp = strchr(line, ':'); + if (!tmp) + return 0; + + *tmp = '\0'; + tmp++; + + if (virStrToLong_uip(line + 1, &line, 10, &level) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Cannot parse resctrl schema level '%s'"), + line + 1); + return -1; + } + + type = virResctrlTypeFromString(line); + if (type < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Cannot parse resctrl schema level '%s'"), + line + 1); + return -1; + } + + caches = virStringSplitCount(tmp, ";", 0, &ncaches); + if (!caches) + return 0; + + for (i = 0; i < ncaches; i++) { + if (virResctrlAllocParseProcessCache(resctrl, level, type, caches[i]) < 0) + goto cleanup; + } + + ret = 0; + cleanup: + virStringListFree(caches); + return ret; +} + +static int +virResctrlAllocParse(virResctrlAllocPtr *alloc, + const char *schemata) +{
The virResctrlAllocPtr object should already exists and this function should only parse the data into existing object.
+ virResctrlAllocPtr tmp = NULL; + char **lines = NULL; + size_t nlines = 0; + size_t i = 0; + int ret = -1; + + lines = virStringSplitCount(schemata, "\n", 0, &nlines); + for (i = 0; i < nlines; i++) { + if (virResctrlAllocParseProcessLine(&tmp, lines[i]) < 0) + goto cleanup; + } + + *alloc = tmp; + tmp = NULL; + ret = 0; + cleanup: + virStringListFree(lines); + virObjectUnref(tmp); + return ret; +} + +static void +virResctrlAllocSubtractPerType(virResctrlAllocPerTypePtr a, + virResctrlAllocPerTypePtr b) +{ + size_t i = 0; + + if (!a || !b) + return; + + for (i = 0; i < a->nmasks && i < b->nmasks; ++i) { + if (a->masks[i] && b->masks[i]) + virBitmapSubtract(a->masks[i], b->masks[i]); + } +} + +static void +virResctrlAllocSubtract(virResctrlAllocPtr a, + virResctrlAllocPtr b) +{ + size_t i = 0; + size_t j = 0; + + if (!b) + return; + + for (i = 0; i < a->nlevels && b->nlevels; ++i) { + if (a->levels[i] && b->levels[i]) { + /* Here we rely on all the system allocations to use the same types. + * We kind of _hope_ it's the case. If this is left here until the + * review and someone finds it, then suggest only removing this last + * sentence. */ + for (j = 0; j < VIR_CACHE_TYPE_LAST; j++) { + virResctrlAllocSubtractPerType(a->levels[i]->types[j], + b->levels[i]->types[j]); + } + } + } +} + +virResctrlAllocPtr +virResctrlAllocNewFromInfo(virResctrlInfoPtr info) +{ + size_t i = 0; + size_t j = 0; + size_t k = 0; + virResctrlAllocPtr ret = NULL; + virBitmapPtr mask = NULL; + + for (i = 0; i < info->nlevels; i++) { + virResctrlInfoPerLevelPtr i_level = info->levels[i]; + + if (!i_level) + continue; + + for (j = 0; j < VIR_CACHE_TYPE_LAST; j++) { + virResctrlInfoPerTypePtr i_type = i_level->types[j]; + + if (!i_type) + continue; + + virBitmapFree(mask); + mask = virBitmapNew(i_type->bits); + if (!mask) + goto error; + virBitmapSetAll(mask); + + for (k = 0; k <= i_type->max_cache_id; k++) { + if (virResctrlAllocUpdateMask(&ret, i, j, k, mask) < 0) + goto error; + } + } + } + + cleanup: + virBitmapFree(mask); + return ret; + error: + virObjectUnref(ret); + ret = NULL; + goto cleanup; +} + +virResctrlAllocPtr +virResctrlAllocGetFree(virResctrlInfoPtr resctrl) +{ + virResctrlAllocPtr ret = NULL; + virResctrlAllocPtr alloc = NULL; + virBitmapPtr mask = NULL; + struct dirent *ent = NULL; + DIR *dirp = NULL; + char *schemata = NULL; + int rv = -1; + + ret = virResctrlAllocNewFromInfo(resctrl); + if (!ret) + return NULL; + + if (virFileReadValueString(&schemata, + SYSFS_RESCTRL_PATH + "/schemata") < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Could not read schemata file for the default group")); + goto error; + } + + if (virResctrlAllocParse(&alloc, schemata) < 0) + goto error; + if (!alloc) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("No schemata for default resctrlfs group")); + goto error; + } + virResctrlAllocSubtract(ret, alloc); + + if (virDirOpen(&dirp, SYSFS_RESCTRL_PATH) < 0) + goto error; + + while ((rv = virDirRead(dirp, &ent, SYSFS_RESCTRL_PATH)) > 0) { + if (ent->d_type != DT_DIR) + continue; + + if (STREQ(ent->d_name, "info")) + continue; + + VIR_FREE(schemata); + if (virFileReadValueString(&schemata, + SYSFS_RESCTRL_PATH + "/%s/schemata", + ent->d_name) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Could not read schemata file for group %s"), + ent->d_name); + goto error; + } + + virObjectUnref(alloc); + alloc = NULL; + if (virResctrlAllocParse(&alloc, schemata) < 0) + goto error; + + virResctrlAllocSubtract(ret, alloc); + + VIR_FREE(schemata); + } + if (rv < 0) + goto error; + + cleanup: + virObjectUnref(alloc); + VIR_DIR_CLOSE(dirp); + VIR_FREE(schemata); + virBitmapFree(mask); + return ret; + + error: + virObjectUnref(ret); + ret = NULL; + goto cleanup; +} + +int +virResctrlAllocMasksAssign(virResctrlInfoPtr r_info, + virResctrlAllocPtr alloc, + void **save_ptr) +{ + int ret = -1; + unsigned int level = 0; + virResctrlAllocPtr alloc_free = NULL; + + if (!r_info) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("Resource control is not supported on this host")); + return -1; + }
I'm wondering whether this error message can be moved to virResctrlAllocCreate() or somewhere else to hit it as soon as possible.
+ + if (!save_ptr) { + alloc_free = virResctrlAllocGetFree(r_info); + } else { + if (!*save_ptr) + *save_ptr = virResctrlAllocGetFree(r_info); + + alloc_free = *save_ptr; + }
This code and the *save_ptr* is here only for tests purposes. Would it be possible to get rid of it? How much time it takes to calculate free allocation?
+ + if (!alloc_free) + return -1; + + for (level = 0; level < alloc->nlevels; level++) { + virResctrlAllocPerLevelPtr a_level = alloc->levels[level]; + virResctrlAllocPerLevelPtr f_level = NULL; + unsigned int type = 0; + + if (!a_level) + continue; + + if (level < alloc_free->nlevels) + f_level = alloc_free->levels[level]; + + if (!f_level) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cache level %d does not support tuning"), + level); + goto cleanup; + } + + for (type = 0; type < VIR_CACHE_TYPE_LAST; type++) { + virResctrlAllocPerTypePtr a_type = a_level->types[type]; + virResctrlAllocPerTypePtr f_type = f_level->types[type]; + unsigned int cache = 0; + + if (!a_type) + continue; + + if (!f_type) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cache level %d does not support tuning for " + "scope type '%s'"), + level, virCacheTypeToString(type)); + goto cleanup; + } + + for (cache = 0; cache < a_type->nsizes; cache++) { + unsigned long long *size = a_type->sizes[cache]; + virBitmapPtr a_mask = NULL; + virBitmapPtr f_mask = f_type->masks[cache]; + virResctrlInfoPerLevelPtr i_level = r_info->levels[level]; + virResctrlInfoPerTypePtr i_type = i_level->types[type]; + unsigned long long granularity; + unsigned long long need_bits; + size_t i = 0; + ssize_t pos = -1; + ssize_t last_bits = 0; + ssize_t last_pos = -1; + + if (!size) + continue; + + if (cache >= f_type->nmasks) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cache with id %u does not exists for level %d"), + cache, level); + goto cleanup; + } + + f_mask = f_type->masks[cache]; + if (!f_mask) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cache level %d id %u does not support tuning for " + "scope type '%s'"), + level, cache, virCacheTypeToString(type)); + goto cleanup; + } + + granularity = i_type->size / i_type->bits; + need_bits = *size / granularity; + + if (*size % granularity) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cache allocation of size %llu is not " + "divisible by granularity %llu"), + *size, granularity); + goto cleanup; + } + + if (need_bits < i_type->min_cbm_bits) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cache allocation of size %llu is smaller " + "than the minimum allowed allocation %llu"), + *size, granularity * i_type->min_cbm_bits); + goto cleanup; + } + + while ((pos = virBitmapNextSetBit(f_mask, pos)) >= 0) { + ssize_t pos_clear = virBitmapNextClearBit(f_mask, pos); + ssize_t bits; + + if (pos_clear < 0) + pos_clear = virBitmapSize(f_mask); + + bits = pos_clear - pos; + + /* Not enough bits, move on and skip all of them */ + if (bits < need_bits) { + pos = pos_clear; + continue; + } + + /* This fits perfectly */ + if (bits == need_bits) { + last_pos = pos; + break; + } + + /* Remember the smaller region if we already found on before */ + if (last_pos < 0 || (last_bits && bits < last_bits)) { + last_bits = bits; + last_pos = pos; + } + + pos = pos_clear; + } + + if (last_pos < 0) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Not enough room for allocation of " + "%llu bytes for level %u cache %u " + "scope type '%s'"), + *size, level, cache, + virCacheTypeToString(type)); + goto cleanup; + } + + a_mask = virBitmapNew(i_type->bits); + for (i = last_pos; i < last_pos + need_bits; i++) { + ignore_value(virBitmapSetBit(a_mask, i)); + ignore_value(virBitmapClearBit(f_mask, i)); + } + + if (a_type->nmasks <= cache) { + if (VIR_EXPAND_N(a_type->masks, a_type->nmasks, + cache - a_type->nmasks + 1) < 0) { + virBitmapFree(a_mask); + goto cleanup; + } + } + a_type->masks[cache] = a_mask; + } + } + } + + ret = 0; + cleanup: + if (!save_ptr) + virObjectUnref(alloc_free); + return ret; +} + +/* This checks if the directory for the alloc exists. If not it tries to create + * it and apply appropriate alloc settings. */ +int +virResctrlAllocCreate(virResctrlInfoPtr r_info, + virResctrlAllocPtr alloc, + const char *drivername, + const char *machinename) +{ + char *alloc_path = NULL; + char *schemata_path = NULL; + bool dir_created = false; + char *alloc_str = NULL; + int ret = -1; + int lockfd = -1; + + if (!alloc) + return 0; + + if (!alloc->path && + virAsprintf(&alloc->path, "%s/%s-%s-%s", + SYSFS_RESCTRL_PATH, drivername, machinename, alloc->id) < 0) + return -1; + + /* Check if this allocation was already created */ + if (virFileIsDir(alloc->path)) { + VIR_FREE(alloc_path); + return 0; + } + + if (virFileExists(alloc->path)) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Path '%s' for resctrl allocation exists and is not a " + "directory"), alloc->path); + goto cleanup; + } + + lockfd = virResctrlLockWrite(); + if (lockfd < 0) + goto cleanup; + + if (virResctrlAllocMasksAssign(r_info, alloc, NULL) < 0) + goto cleanup; + + alloc_str = virResctrlAllocFormat(alloc); + if (!alloc_str) + return -1; + + if (virAsprintf(&schemata_path, "%s/schemata", alloc->path) < 0) + goto cleanup; + + if (virFileMakePath(alloc->path) < 0) { + virReportSystemError(errno, + _("Cannot create resctrl directory '%s'"), + alloc->path); + goto cleanup; + } + dir_created = true; + + if (virFileWriteStr(schemata_path, alloc_str, 0) < 0) { + virReportSystemError(errno, + _("Cannot write into schemata file '%s'"), + schemata_path); + goto cleanup; + } + + ret = 0; + cleanup: + if (ret < 0 && dir_created) + rmdir(alloc->path); + virResctrlUnlock(lockfd); + VIR_FREE(alloc_str); + VIR_FREE(alloc_path); + VIR_FREE(schemata_path); + return ret; +} + +int +virResctrlAllocAddPID(virResctrlAllocPtr alloc, + pid_t pid) +{ + char *tasks = NULL; + char *pidstr = NULL; + int ret = 0; + + if (!alloc->path) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Cannot add pid to non-existing resctrl allocation")); + return -1; + } + + if (virAsprintf(&tasks, "%s/tasks", alloc->path) < 0) + return -1; + + if (virAsprintf(&pidstr, "%lld", (long long int) pid) < 0) + goto cleanup; + + if (virFileWriteStr(tasks, pidstr, 0) < 0) { + virReportSystemError(errno, + _("Cannot write pid in tasks file '%s'"), + tasks); + goto cleanup; + } + + ret = 0; + cleanup: + VIR_FREE(tasks); + VIR_FREE(pidstr); + return ret; +} + +int +virResctrlAllocRemove(virResctrlAllocPtr alloc) +{ + int ret = 0; + + if (!alloc->path) + return 0; + + VIR_DEBUG("Removing resctrl allocation %s", alloc->path); + if (rmdir(alloc->path) != 0 && errno != ENOENT) { + ret = -errno; + VIR_ERROR(_("Unable to remove %s (%d)"), alloc->path, errno); + } + + return ret; +} diff --git a/src/util/virresctrl.h b/src/util/virresctrl.h index c4df88f23c3a..b233eca41c03 100644 --- a/src/util/virresctrl.h +++ b/src/util/virresctrl.h @@ -62,4 +62,63 @@ virResctrlInfoGetCache(virResctrlInfoPtr resctrl, size_t *ncontrols, virResctrlInfoPerCachePtr **controls);
+/* Alloc-related things */ +typedef struct _virResctrlAlloc virResctrlAlloc; +typedef virResctrlAlloc *virResctrlAllocPtr; + +typedef int virResctrlAllocForeachSizeCallback(unsigned int level, + virCacheType type, + unsigned int cache, + unsigned long long size, + void *opaque); + +virResctrlAllocPtr +virResctrlAllocNewFromInfo(virResctrlInfoPtr info); + +int +virResctrlAllocUpdateSize(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + unsigned long long size); + +int +virResctrlAllocUpdateMask(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + virBitmapPtr mask); + +int +virResctrlAllocSetSize(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + unsigned long long size); + +int +virResctrlAllocForeachSize(virResctrlAllocPtr resctrl, + virResctrlAllocForeachSizeCallback cb, + void *opaque); + +int +virResctrlAllocSetID(virResctrlAllocPtr alloc, + const char *id); + +char * +virResctrlAllocFormat(virResctrlAllocPtr alloc); + +int +virResctrlAllocCreate(virResctrlInfoPtr r_info, + virResctrlAllocPtr alloc, + const char *drivername, + const char *machinename); + +int +virResctrlAllocAddPID(virResctrlAllocPtr alloc, + pid_t pid); + +int +virResctrlAllocRemove(virResctrlAllocPtr alloc); + #endif /* __VIR_RESCTRL_H__ */ diff --git a/src/util/virresctrlpriv.h b/src/util/virresctrlpriv.h new file mode 100644 index 000000000000..4255ad496302 --- /dev/null +++ b/src/util/virresctrlpriv.h @@ -0,0 +1,32 @@ +/* + * virresctrlpriv.h: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + */ + +#ifndef __VIR_RESCTRL_PRIV_H__ +# define __VIR_RESCTRL_PRIV_H__ + +# include "virresctrl.h" + +virResctrlAllocPtr +virResctrlAllocGetFree(virResctrlInfoPtr resctrl); + +int +virResctrlAllocMasksAssign(virResctrlInfoPtr r_info, + virResctrlAllocPtr alloc, + void **save_ptr); + +#endif /* __VIR_RESCTRL_PRIV_H__ */ -- 2.15.0
-- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list

[...]
/* Info-related functions */ int virResctrlGetInfo(virResctrlInfoPtr *resctrl) @@ -318,3 +465,866 @@ virResctrlInfoGetCache(virResctrlInfoPtr resctrl, VIR_FREE(*controls); goto cleanup; } + + +/* Alloc-related functions */ +static virResctrlAllocPerTypePtr +virResctrlAllocFindType(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + bool alloc) +{
I don't like this implementation, it's too complex and it does two different things based on a bool attribute. I see the benefit that it's convenient but IMHO it's ugly.
Well I stared at it a *long* time before coming to the conclusion that as odd looking as it is - it does what it's supposed to do in a unique way compared to other libvirt functions. While yes a bit ugly, it didn't feel too complex once I got the basic premise. I also kept thinking this whole sequence relies on the "original caller" (e.g. where &resctrl originally gets passed) to be sure on failure to Unref - tracing back to that was a challenge. Thinking about these functions being called in the middle of some other code - I dunno. Still Like I pointed out - it would help *a lot* to document WTF is going on! Returning the "address of" some location based on array entry/offset does cause some concern - I was able to eventually convince myself it works... Although I must say I studied virResctrlAllocUpdateMask for quite a bit trying to determine whether what it claimed to do was actually being done. John
The only call path that doesn't need allocation is from virResctrlAllocCheckCollision(). The remaining two calls virResctrlAllocUpdateMask() and virResctrlAllocUpdateSize() needs to allocate the internal structures of *virResctrlAllocPtr* object.
Another point is that there is no need to have this function create new *virResctrlAllocPtr* object on demand, I would prefer creating that object in advance before we even start filling all the data.
+ virResctrlAllocPerLevelPtr a_level = NULL; + virResctrlAllocPtr tmp = NULL; + + if (!*resctrl) { + if (!alloc || !(*resctrl = virResctrlAllocNew())) + return NULL; + } + + tmp = *resctrl; + + /* Per-level array */ + if (tmp->nlevels <= level) { + if (!alloc || VIR_EXPAND_N(tmp->levels, tmp->nlevels, + level - tmp->nlevels + 1) < 0) + return NULL; + } + + if (!tmp->levels[level]) { + if (!alloc || + VIR_ALLOC(tmp->levels[level]) < 0 || + VIR_ALLOC_N(tmp->levels[level]->types, VIR_CACHE_TYPE_LAST) < 0) + return NULL; + } + a_level = tmp->levels[level]; + + if (!a_level->types[type]) { + if (!alloc || VIR_ALLOC(a_level->types[type]) < 0) + return NULL; + } + + return a_level->types[type]; +} + +static virBitmapPtr * +virResctrlAllocFindMask(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + bool alloc) +{
The code of this function can be merged into virResctrlAllocUpdateMask() and again only allocate the structures and set the mask. Currently there is no need for "Find" function if we will need it in the future it should definitely only find the mask, not allocate it.
+ virResctrlAllocPerTypePtr a_type = virResctrlAllocFindType(resctrl, level, + type, alloc); + + if (!a_type) + return NULL; + + if (!a_type->masks) { + if (!alloc || VIR_ALLOC_N(a_type->masks, cache + 1) < 0) + return NULL; + a_type->nmasks = cache + 1; + } else if (a_type->nmasks <= cache) { + if (!alloc || VIR_EXPAND_N(a_type->masks, a_type->nmasks, + cache - a_type->nmasks + 1) < 0) + return NULL; + } + + return a_type->masks + cache; +} + +static unsigned long long * +virResctrlAllocFindSize(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + bool alloc) +{ + virResctrlAllocPerTypePtr a_type = virResctrlAllocFindType(resctrl, level, + type, alloc);
Same as for virResctrlAllocFindMask(). With the exception that we actually need lookup function so create a one, that will only check whether there is some size set or not.
+ + if (!a_type) + return NULL; + + if (!a_type->sizes) { + if (!alloc || VIR_ALLOC_N(a_type->sizes, cache + 1) < 0) + return NULL; + a_type->nsizes = cache + 1; + } else if (a_type->nsizes <= cache) { + if (!alloc || VIR_EXPAND_N(a_type->sizes, a_type->nsizes, + cache - a_type->nsizes + 1) < 0) + return NULL; + } + + if (!a_type->sizes[cache]) { + if (!alloc || VIR_ALLOC(a_type->sizes[cache]) < 0) + return NULL; + } + + return a_type->sizes[cache]; +} + +int +virResctrlAllocUpdateMask(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + virBitmapPtr mask) +{ + virBitmapPtr *found = virResctrlAllocFindMask(resctrl, level, type, cache, + true); + + if (!found) + return -1; + + virBitmapFree(*found); + + *found = virBitmapNew(virBitmapSize(mask)); + if (!*found) + return -1; + + return virBitmapCopy(*found, mask); +} + +int +virResctrlAllocUpdateSize(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + unsigned long long size) +{ + unsigned long long *found = virResctrlAllocFindSize(resctrl, level, type, + cache, true); + + if (!found) + return -1; + + *found = size; + return 0; +} + +static bool +virResctrlAllocCheckCollision(virResctrlAllocPtr a, + unsigned int level, + virCacheType type, + unsigned int cache) +{ + /* If there is an allocation for type 'both', there can be no other + * allocation for the same cache */ + if (virResctrlAllocFindSize(&a, level, VIR_CACHE_TYPE_BOTH, cache, false)) + return true; + + if (type == VIR_CACHE_TYPE_BOTH) { + if (virResctrlAllocFindSize(&a, level, VIR_CACHE_TYPE_CODE, cache, false)) + return true; + if (virResctrlAllocFindSize(&a, level, VIR_CACHE_TYPE_DATA, cache, false)) + return true; + } + + /* And never two allocations for the same type */ + if (virResctrlAllocFindSize(&a, level, type, cache, false)) + return true; + + return false; +} + +int +virResctrlAllocSetSize(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + unsigned long long size) +{ + if (virResctrlAllocCheckCollision(*resctrl, level, type, cache)) { + virReportError(VIR_ERR_XML_ERROR, + _("Colliding cache allocations for cache " + "level '%u' id '%u', type '%s'"), + level, cache, virCacheTypeToString(type)); + return -1; + } + + return virResctrlAllocUpdateSize(resctrl, level, type, cache, size); +} + +int +virResctrlAllocForeachSize(virResctrlAllocPtr resctrl, + virResctrlAllocForeachSizeCallback cb, + void *opaque) +{ + int ret = 0; + unsigned int level = 0; + unsigned int type = 0; + unsigned int cache = 0; + + if (!resctrl) + return 0; + + for (level = 0; level < resctrl->nlevels; level++) { + virResctrlAllocPerLevelPtr a_level = resctrl->levels[level]; + + if (!a_level) + continue; + + for (type = 0; type < VIR_CACHE_TYPE_LAST; type++) { + virResctrlAllocPerTypePtr a_type = a_level->types[type]; + + if (!a_type) + continue; + + for (cache = 0; cache < a_type->nsizes; cache++) { + unsigned long long *size = a_type->sizes[cache]; + + if (!size) + continue; + + ret = cb(level, type, cache, *size, opaque); + if (ret < 0) + return ret; + } + } + } + + return 0; +} + +int +virResctrlAllocSetID(virResctrlAllocPtr alloc, + const char *id) +{ + if (!id) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Resctrl allocation id cannot be NULL")); + return -1; + } + + return VIR_STRDUP(alloc->id, id); +}
This is how I would expect all the other public functions to look like. Simple, does one thing and there is no magic.
[...]

On Wed, Nov 15, 2017 at 07:14:28PM +0100, Pavel Hrdina wrote:
On Mon, Nov 13, 2017 at 09:50:31AM +0100, Martin Kletzander wrote:
With this commit we finally have a way to read and manipulate basic resctrl settings. Locking is done only on exposed functions that read/write from/to resctrlfs. Not in fuctions that are exposed in virresctrlpriv.h as those are only supposed to be used from tests.
Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- src/Makefile.am | 2 +- src/libvirt_private.syms | 12 + src/util/virresctrl.c | 1012 ++++++++++++++++++++++++++++++++++++++++++++- src/util/virresctrl.h | 59 +++ src/util/virresctrlpriv.h | 32 ++ 5 files changed, 1115 insertions(+), 2 deletions(-) create mode 100644 src/util/virresctrlpriv.h
diff --git a/src/Makefile.am b/src/Makefile.am index 1d24231249de..ad113262fbb0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -167,7 +167,7 @@ UTIL_SOURCES = \ util/virprocess.c util/virprocess.h \ util/virqemu.c util/virqemu.h \ util/virrandom.h util/virrandom.c \ - util/virresctrl.h util/virresctrl.c \ + util/virresctrl.h util/virresctrl.c util/virresctrlpriv.h \
Use only single space instead of tab after "util/virresctrl.c" and "util/virresctrlpriv.h".
That is actualy a single blank. It was a TAB that I didn't see in the code, but here, since it is one more character to the right, it shows. Again I don't see it in this reply as it again aligned differently :) [...]
@@ -318,3 +465,866 @@ virResctrlInfoGetCache(virResctrlInfoPtr resctrl, VIR_FREE(*controls); goto cleanup; } + + +/* Alloc-related functions */ +static virResctrlAllocPerTypePtr +virResctrlAllocFindType(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + bool alloc) +{
I don't like this implementation, it's too complex and it does two different things based on a bool attribute. I see the benefit that it's convenient but IMHO it's ugly.
The only call path that doesn't need allocation is from virResctrlAllocCheckCollision(). The remaining two calls virResctrlAllocUpdateMask() and virResctrlAllocUpdateSize() needs to allocate the internal structures of *virResctrlAllocPtr* object.
I'll duplicate the code if that's what's desired. I guess it will not look as gross as this then.
Another point is that there is no need to have this function create new *virResctrlAllocPtr* object on demand, I would prefer creating that object in advance before we even start filling all the data.
Just to make sure we are on the same page benefit-wise. There actually is. It will only be created if anyone adds size or mask to the allocation, otherwise it is NULL. It is easy to check that the allocation is empty. I'll redo it your way, but you need to have new object created, then update some stuff for it and then have a function that checks if the allocation is empty. And that needs three nested loops which there are too many already in this. [...]
+ +static virBitmapPtr * +virResctrlAllocFindMask(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + bool alloc) +{
The code of this function can be merged into virResctrlAllocUpdateMask() and again only allocate the structures and set the mask. Currently there is no need for "Find" function if we will need it in the future it should definitely only find the mask, not allocate it.
This is here just for separation. I can just cut-n-paste it into the other function. The same with other ones. It sill just create bigger functions that are IMNSHO less readable. Sure I can do that. [...]
+int +virResctrlAllocSetID(virResctrlAllocPtr alloc, + const char *id) +{ + if (!id) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Resctrl allocation id cannot be NULL")); + return -1; + } + + return VIR_STRDUP(alloc->id, id); +}
This is how I would expect all the other public functions to look like. Simple, does one thing and there is no magic.
Well, because it does totally nothing. If all "public" functions would do this then there would be no functionality =D [...]
+static int +virResctrlAllocParse(virResctrlAllocPtr *alloc, + const char *schemata) +{
The virResctrlAllocPtr object should already exists and this function should only parse the data into existing object.
Same as above, but OK, I want to get rid of this patchset, finally. [...]
+int +virResctrlAllocMasksAssign(virResctrlInfoPtr r_info, + virResctrlAllocPtr alloc, + void **save_ptr) +{ + int ret = -1; + unsigned int level = 0; + virResctrlAllocPtr alloc_free = NULL; + + if (!r_info) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("Resource control is not supported on this host")); + return -1; + }
I'm wondering whether this error message can be moved to virResctrlAllocCreate() or somewhere else to hit it as soon as possible.
The resctrl-related decision should not be moved out of virresctrl, so it can be moved to AllocCreate(), but not more up. Even then I would rather duplicate it instead of moving it there so that this function can be used on its own (for tests).
+ + if (!save_ptr) { + alloc_free = virResctrlAllocGetFree(r_info); + } else { + if (!*save_ptr) + *save_ptr = virResctrlAllocGetFree(r_info); + + alloc_free = *save_ptr; + }
This code and the *save_ptr* is here only for tests purposes. Would it be possible to get rid of it? How much time it takes to calculate free allocation?
Not test purposes, it keeps the current state. Think of it as char **saveptr in strtok_r() and similar. How much time it takes? Depends on how much allocations you have. Crawling through the sysfs tree, doing bunch of allocations here and there. You need to run virResctrlAllocGetFree() on every entry. It's relatively fast, but pointless. But if I do that, not only I can remove these 8 lines of code, but also ...
+ + if (!alloc_free) + return -1; + + for (level = 0; level < alloc->nlevels; level++) { + virResctrlAllocPerLevelPtr a_level = alloc->levels[level]; + virResctrlAllocPerLevelPtr f_level = NULL; + unsigned int type = 0; + + if (!a_level) + continue; + + if (level < alloc_free->nlevels) + f_level = alloc_free->levels[level]; + + if (!f_level) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cache level %d does not support tuning"), + level); + goto cleanup; + } + + for (type = 0; type < VIR_CACHE_TYPE_LAST; type++) { + virResctrlAllocPerTypePtr a_type = a_level->types[type]; + virResctrlAllocPerTypePtr f_type = f_level->types[type]; + unsigned int cache = 0; + + if (!a_type) + continue; + + if (!f_type) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cache level %d does not support tuning for " + "scope type '%s'"), + level, virCacheTypeToString(type)); + goto cleanup; + } + + for (cache = 0; cache < a_type->nsizes; cache++) { + unsigned long long *size = a_type->sizes[cache]; + virBitmapPtr a_mask = NULL; + virBitmapPtr f_mask = f_type->masks[cache]; + virResctrlInfoPerLevelPtr i_level = r_info->levels[level]; + virResctrlInfoPerTypePtr i_type = i_level->types[type]; + unsigned long long granularity; + unsigned long long need_bits; + size_t i = 0; + ssize_t pos = -1; + ssize_t last_bits = 0; + ssize_t last_pos = -1; + + if (!size) + continue; + + if (cache >= f_type->nmasks) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cache with id %u does not exists for level %d"), + cache, level); + goto cleanup; + } + + f_mask = f_type->masks[cache]; + if (!f_mask) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cache level %d id %u does not support tuning for " + "scope type '%s'"), + level, cache, virCacheTypeToString(type)); + goto cleanup; + } + + granularity = i_type->size / i_type->bits; + need_bits = *size / granularity; + + if (*size % granularity) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cache allocation of size %llu is not " + "divisible by granularity %llu"), + *size, granularity); + goto cleanup; + } + + if (need_bits < i_type->min_cbm_bits) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cache allocation of size %llu is smaller " + "than the minimum allowed allocation %llu"), + *size, granularity * i_type->min_cbm_bits); + goto cleanup; + } + + while ((pos = virBitmapNextSetBit(f_mask, pos)) >= 0) { + ssize_t pos_clear = virBitmapNextClearBit(f_mask, pos); + ssize_t bits; + + if (pos_clear < 0) + pos_clear = virBitmapSize(f_mask); + + bits = pos_clear - pos; + + /* Not enough bits, move on and skip all of them */ + if (bits < need_bits) { + pos = pos_clear; + continue; + } + + /* This fits perfectly */ + if (bits == need_bits) { + last_pos = pos; + break; + } + + /* Remember the smaller region if we already found on before */ + if (last_pos < 0 || (last_bits && bits < last_bits)) { + last_bits = bits; + last_pos = pos; + } + + pos = pos_clear; + } + + if (last_pos < 0) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Not enough room for allocation of " + "%llu bytes for level %u cache %u " + "scope type '%s'"), + *size, level, cache, + virCacheTypeToString(type)); + goto cleanup; + } + + a_mask = virBitmapNew(i_type->bits); + for (i = last_pos; i < last_pos + need_bits; i++) { + ignore_value(virBitmapSetBit(a_mask, i)); + ignore_value(virBitmapClearBit(f_mask, i));
... this one.

On Tue, Nov 21, 2017 at 10:49:29AM +0100, Martin Kletzander wrote:
On Wed, Nov 15, 2017 at 07:14:28PM +0100, Pavel Hrdina wrote:
On Mon, Nov 13, 2017 at 09:50:31AM +0100, Martin Kletzander wrote:
With this commit we finally have a way to read and manipulate basic resctrl settings. Locking is done only on exposed functions that read/write from/to resctrlfs. Not in fuctions that are exposed in virresctrlpriv.h as those are only supposed to be used from tests.
Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- src/Makefile.am | 2 +- src/libvirt_private.syms | 12 + src/util/virresctrl.c | 1012 ++++++++++++++++++++++++++++++++++++++++++++- src/util/virresctrl.h | 59 +++ src/util/virresctrlpriv.h | 32 ++ 5 files changed, 1115 insertions(+), 2 deletions(-) create mode 100644 src/util/virresctrlpriv.h
diff --git a/src/Makefile.am b/src/Makefile.am index 1d24231249de..ad113262fbb0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -167,7 +167,7 @@ UTIL_SOURCES = \ util/virprocess.c util/virprocess.h \ util/virqemu.c util/virqemu.h \ util/virrandom.h util/virrandom.c \ - util/virresctrl.h util/virresctrl.c \ + util/virresctrl.h util/virresctrl.c util/virresctrlpriv.h \
Use only single space instead of tab after "util/virresctrl.c" and "util/virresctrlpriv.h".
That is actualy a single blank. It was a TAB that I didn't see in the code, but here, since it is one more character to the right, it shows. Again I don't see it in this reply as it again aligned differently :)
I opened the patch in vim and configured to display TAB and there are two TABs, so please double check it in your editor :).
[...]
@@ -318,3 +465,866 @@ virResctrlInfoGetCache(virResctrlInfoPtr resctrl, VIR_FREE(*controls); goto cleanup; } + + +/* Alloc-related functions */ +static virResctrlAllocPerTypePtr +virResctrlAllocFindType(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + bool alloc) +{
I don't like this implementation, it's too complex and it does two different things based on a bool attribute. I see the benefit that it's convenient but IMHO it's ugly.
The only call path that doesn't need allocation is from virResctrlAllocCheckCollision(). The remaining two calls virResctrlAllocUpdateMask() and virResctrlAllocUpdateSize() needs to allocate the internal structures of *virResctrlAllocPtr* object.
I'll duplicate the code if that's what's desired. I guess it will not look as gross as this then.
Another point is that there is no need to have this function create new *virResctrlAllocPtr* object on demand, I would prefer creating that object in advance before we even start filling all the data.
Just to make sure we are on the same page benefit-wise. There actually is. It will only be created if anyone adds size or mask to the allocation, otherwise it is NULL. It is easy to check that the allocation is empty. I'll redo it your way, but you need to have new object created, then update some stuff for it and then have a function that checks if the allocation is empty. And that needs three nested loops which there are too many already in this.
So the allocation happens in these public functions: * virResctrlAllocNewFromInfo() - There you know based on Info whether you need to allocate new object or not. This is probably the only case where the automatic allocation helps a little bit. * virResctrlAllocSetSize() - This is indirectly called from virDomainCachetuneDefParse() where you know if there is any cache element to parse so you can allocate new object if there is anything to set. So there shouldn't be any need to have a function that will check the allocation. Otherwise I wouldn't have suggested this modification.
[...]
+ +static virBitmapPtr * +virResctrlAllocFindMask(virResctrlAllocPtr *resctrl, + unsigned int level, + virCacheType type, + unsigned int cache, + bool alloc) +{
The code of this function can be merged into virResctrlAllocUpdateMask() and again only allocate the structures and set the mask. Currently there is no need for "Find" function if we will need it in the future it should definitely only find the mask, not allocate it.
This is here just for separation. I can just cut-n-paste it into the other function. The same with other ones. It sill just create bigger functions that are IMNSHO less readable. Sure I can do that.
I think that you can decide to merge the functions or not based on the resulting code after it is rewritten. It's not requirement, more like suggestion based on what I was imagine how the new code could look like.
[...]
+int +virResctrlAllocSetID(virResctrlAllocPtr alloc, + const char *id) +{ + if (!id) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Resctrl allocation id cannot be NULL")); + return -1; + } + + return VIR_STRDUP(alloc->id, id); +}
This is how I would expect all the other public functions to look like. Simple, does one thing and there is no magic.
Well, because it does totally nothing. If all "public" functions would do this then there would be no functionality =D
[...]
+static int +virResctrlAllocParse(virResctrlAllocPtr *alloc, + const char *schemata) +{
The virResctrlAllocPtr object should already exists and this function should only parse the data into existing object.
Same as above, but OK, I want to get rid of this patchset, finally.
[...]
+int +virResctrlAllocMasksAssign(virResctrlInfoPtr r_info, + virResctrlAllocPtr alloc, + void **save_ptr) +{ + int ret = -1; + unsigned int level = 0; + virResctrlAllocPtr alloc_free = NULL; + + if (!r_info) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("Resource control is not supported on this host")); + return -1; + }
I'm wondering whether this error message can be moved to virResctrlAllocCreate() or somewhere else to hit it as soon as possible.
The resctrl-related decision should not be moved out of virresctrl, so it can be moved to AllocCreate(), but not more up. Even then I would rather duplicate it instead of moving it there so that this function can be used on its own (for tests).
Right, if it makes sense to use it as standalone function the error message should probably stay here.
+ + if (!save_ptr) { + alloc_free = virResctrlAllocGetFree(r_info); + } else { + if (!*save_ptr) + *save_ptr = virResctrlAllocGetFree(r_info); + + alloc_free = *save_ptr; + }
This code and the *save_ptr* is here only for tests purposes. Would it be possible to get rid of it? How much time it takes to calculate free allocation?
Not test purposes, it keeps the current state. Think of it as char **saveptr in strtok_r() and similar. How much time it takes? Depends on how much allocations you have. Crawling through the sysfs tree, doing bunch of allocations here and there. You need to run virResctrlAllocGetFree() on every entry. It's relatively fast, but pointless. But if I do that, not only I can remove these 8 lines of code, but also ...
I understand that it saves current state so you don't have to parse it again but from the patch series it looks like it is used only for tests. So the question about speed was only related to tests where you use this optimization. Pavel

Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- docs/formatdomain.html.in | 24 ++ docs/schemas/domaincommon.rng | 32 +++ src/conf/domain_conf.c | 249 +++++++++++++++++++++ src/conf/domain_conf.h | 21 ++ src/util/virresctrl.c | 2 +- .../genericxml2xmlindata/generic-cachetune-cdp.xml | 36 +++ .../generic-cachetune-colliding-allocs.xml | 30 +++ .../generic-cachetune-colliding-tunes.xml | 32 +++ .../generic-cachetune-colliding-types.xml | 30 +++ .../generic-cachetune-small.xml | 29 +++ tests/genericxml2xmlindata/generic-cachetune.xml | 33 +++ tests/genericxml2xmltest.c | 10 + 12 files changed, 527 insertions(+), 1 deletion(-) create mode 100644 tests/genericxml2xmlindata/generic-cachetune-cdp.xml create mode 100644 tests/genericxml2xmlindata/generic-cachetune-colliding-allocs.xml create mode 100644 tests/genericxml2xmlindata/generic-cachetune-colliding-tunes.xml create mode 100644 tests/genericxml2xmlindata/generic-cachetune-colliding-types.xml create mode 100644 tests/genericxml2xmlindata/generic-cachetune-small.xml create mode 100644 tests/genericxml2xmlindata/generic-cachetune.xml diff --git a/docs/formatdomain.html.in b/docs/formatdomain.html.in index 92e14a919aba..b9d7f53b31f9 100644 --- a/docs/formatdomain.html.in +++ b/docs/formatdomain.html.in @@ -689,6 +689,10 @@ <iothread_quota>-1</iothread_quota> <vcpusched vcpus='0-4,^3' scheduler='fifo' priority='1'/> <iothreadsched iothreads='2' scheduler='batch'/> + <cachetune vcpus='0-3'> + <cache id='0' level='3' type='both' size='3' unit='MiB'/> + <cache id='1' level='3' type='both' size='3' unit='MiB'/> + </cachetune> </cputune> ... </domain> @@ -834,6 +838,26 @@ <span class="since">Since 1.2.13</span> </dd> + <dt><code>cachetune</code></dt> + <dd> + Optional <code>cachetune</code> element can control allocations for CPU + caches using the resctrlfs on the host. Whether or not is this supported + can be gathered from capabilities where some limitations like minimum + size and required granularity are reported as well. + Attribute <code>vcpus</code> specifies what vCPUs this allocation + applies to. One vCPU cannot have multiple <code>cachetune</code> + allocations. For now there is only one supported + sub-element, <code>cache</code>. That element must have + attributes <code>level</code> and <code>id</code> - host's cache level/id + to allocate from, <code>type</code> - whether to + allocate <code>code</code>, <code>data</code> or <code>both</code> + and <code>size</code> - size of the region to allocate. Size is by + default in bytes, but optional attribute <code>unit</code> can be used + to scale the size appropriately. Each <code>cachetune</code> can have + multiple <code>cache</code> elements. <span class="since">Since + 3.10.0</span> + + </dd> </dl> diff --git a/docs/schemas/domaincommon.rng b/docs/schemas/domaincommon.rng index 9cec1a063724..5d90247799ad 100644 --- a/docs/schemas/domaincommon.rng +++ b/docs/schemas/domaincommon.rng @@ -900,6 +900,38 @@ <ref name="schedparam"/> </element> </zeroOrMore> + <zeroOrMore> + <element name="cachetune"> + <attribute name="vcpus"> + <ref name='cpuset'/> + </attribute> + <oneOrMore> + <element name="cache"> + <attribute name="id"> + <ref name='unsignedInt'/> + </attribute> + <attribute name="level"> + <ref name='unsignedInt'/> + </attribute> + <attribute name="type"> + <choice> + <value>both</value> + <value>code</value> + <value>data</value> + </choice> + </attribute> + <attribute name="size"> + <ref name='unsignedLong'/> + </attribute> + <optional> + <attribute name='unit'> + <ref name='unit'/> + </attribute> + </optional> + </element> + </oneOrMore> + </element> + </zeroOrMore> </interleave> </element> </define> diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 400e900325f2..bf9e61efc8d2 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -2848,6 +2848,17 @@ virDomainLoaderDefFree(virDomainLoaderDefPtr loader) VIR_FREE(loader); } +static void +virDomainCachetuneDefFree(virDomainCachetuneDefPtr cachetune) +{ + if (!cachetune) + return; + + virObjectUnref(cachetune->alloc); + virBitmapFree(cachetune->vcpus); + VIR_FREE(cachetune); +} + void virDomainDefFree(virDomainDefPtr def) { size_t i; @@ -3020,6 +3031,10 @@ void virDomainDefFree(virDomainDefPtr def) virDomainShmemDefFree(def->shmems[i]); VIR_FREE(def->shmems); + for (i = 0; i < def->ncachetunes; i++) + virDomainCachetuneDefFree(def->cachetunes[i]); + VIR_FREE(def->cachetunes); + VIR_FREE(def->keywrap); if (def->namespaceData && def->ns.free) @@ -18080,6 +18095,163 @@ virDomainDefParseBootOptions(virDomainDefPtr def, } +static int +virDomainCachetuneDefParseCache(xmlXPathContextPtr ctxt, + xmlNodePtr node, + virResctrlAllocPtr *alloc) +{ + xmlNodePtr oldnode = ctxt->node; + unsigned int level; + unsigned int cache; + int type; + unsigned long long size; + char *tmp = NULL; + int ret = -1; + + ctxt->node = node; + + tmp = virXMLPropString(node, "id"); + if (!tmp) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("Missing attribute id for cachetune/cache")); + goto cleanup; + } + if (virStrToLong_uip(tmp, NULL, 10, &cache) < 0) { + virReportError(VIR_ERR_XML_ERROR, + _("Invalid attribute id '%s' for cachetune/cache"), + tmp); + goto cleanup; + } + VIR_FREE(tmp); + + tmp = virXMLPropString(node, "level"); + if (!tmp) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("Missing attribute level for cachetune/cache")); + goto cleanup; + } + if (virStrToLong_uip(tmp, NULL, 10, &level) < 0) { + virReportError(VIR_ERR_XML_ERROR, + _("Invalid attribute level '%s' for cachetune/cache"), + tmp); + goto cleanup; + } + VIR_FREE(tmp); + + tmp = virXMLPropString(node, "type"); + if (!tmp) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("Missing attribute type for cachetune/cache")); + goto cleanup; + } + type = virCacheTypeFromString(tmp); + if (type < 0) { + virReportError(VIR_ERR_XML_ERROR, + _("Invalid attribute type '%s' for cachetune"), + tmp); + goto cleanup; + } + VIR_FREE(tmp); + + if (virDomainParseScaledValue("./@size", "./@unit", + ctxt, &size, 1024, + ULLONG_MAX, true) < 0) + goto cleanup; + + if (virResctrlAllocSetSize(alloc, level, type, cache, size) < 0) + goto cleanup; + + ret = 0; + cleanup: + ctxt->node = oldnode; + VIR_FREE(tmp); + return ret; +} + +static int +virDomainCachetuneDefParse(virDomainDefPtr def, + xmlXPathContextPtr ctxt, + xmlNodePtr node) +{ + xmlNodePtr oldnode = ctxt->node; + xmlNodePtr *nodes = NULL; + virBitmapPtr vcpus = NULL; + virResctrlAllocPtr alloc = NULL; + virDomainCachetuneDefPtr tmp_cachetune = NULL; + char *tmp = NULL; + char *vcpus_str = NULL; + ssize_t i = 0; + int n; + int ret = -1; + + ctxt->node = node; + + if (VIR_ALLOC(tmp_cachetune) < 0) + goto cleanup; + + vcpus_str = virXMLPropString(node, "vcpus"); + if (!vcpus_str) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("Missing attribute vcpus for cachetune")); + goto cleanup; + } + if (virBitmapParse(vcpus_str, &vcpus, VIR_DOMAIN_CPUMASK_LEN) < 0) { + virReportError(VIR_ERR_XML_ERROR, + _("Invalid attribute vcpus '%s' for cachetune"), + vcpus_str); + goto cleanup; + } + + if ((n = virXPathNodeSet("./cache", ctxt, &nodes)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("cannot extract cachetune/cache nodes")); + goto cleanup; + } + + for (i = 0; i < n; i++) { + if (virDomainCachetuneDefParseCache(ctxt, nodes[i], &alloc) < 0) + goto cleanup; + } + + if (!alloc) { + ret = 0; + goto cleanup; + } + + virBitmapShrink(vcpus, def->maxvcpus); + + for (i = 0; i < def->ncachetunes; i++) { + if (virBitmapOverlaps(def->cachetunes[i]->vcpus, vcpus)) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("Overlapping vcpus in cachetunes")); + goto cleanup; + } + } + + if (virResctrlAllocSetID(alloc, vcpus_str) < 0) + goto cleanup; + + tmp_cachetune->vcpus = vcpus; + tmp_cachetune->alloc = alloc; + vcpus = NULL; + alloc = NULL; + + if (VIR_APPEND_ELEMENT(def->cachetunes, def->ncachetunes, tmp_cachetune) < 0) + goto cleanup; + + ret = 0; + cleanup: + ctxt->node = oldnode; + virDomainCachetuneDefFree(tmp_cachetune); + virObjectUnref(alloc); + virBitmapFree(vcpus); + VIR_FREE(vcpus_str); + VIR_FREE(nodes); + VIR_FREE(tmp); + return ret; +} + + static virDomainDefPtr virDomainDefParseXML(xmlDocPtr xml, xmlNodePtr root, @@ -18632,6 +18804,18 @@ virDomainDefParseXML(xmlDocPtr xml, } VIR_FREE(nodes); + if ((n = virXPathNodeSet("./cputune/cachetune", ctxt, &nodes)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("cannot extract cachetune nodes")); + goto error; + } + + for (i = 0; i < n; i++) { + if (virDomainCachetuneDefParse(def, ctxt, nodes[i]) < 0) + goto error; + } + VIR_FREE(nodes); + if (virCPUDefParseXML(ctxt, "./cpu[1]", VIR_CPU_TYPE_GUEST, &def->cpu) < 0) goto error; @@ -25444,6 +25628,68 @@ virDomainSchedulerFormat(virBufferPtr buf, } +struct virCachetuneHelperData { + virBufferPtr buf; + size_t vcpu_id; +}; + +static int +virDomainCachetuneDefFormatHelper(unsigned int level, + virCacheType type, + unsigned int cache, + unsigned long long size, + void *opaque) +{ + const char *unit; + virBufferPtr buf = opaque; + unsigned long long short_size = virPrettySize(size, &unit); + + virBufferAsprintf(buf, + "<cache id='%u' level='%u' type='%s' " + "size='%llu' unit='%s'/>\n", + cache, level, virCacheTypeToString(type), + short_size, unit); + + return 0; +} + + +static int +virDomainCachetuneDefFormat(virBufferPtr buf, + virDomainCachetuneDefPtr cachetune) +{ + virBuffer childrenBuf = VIR_BUFFER_INITIALIZER; + char *vcpus = NULL; + int ret = -1; + + virBufferSetChildIndent(&childrenBuf, buf); + virResctrlAllocForeachSize(cachetune->alloc, + virDomainCachetuneDefFormatHelper, + &childrenBuf); + + + if (virBufferCheckError(&childrenBuf) < 0) + goto cleanup; + + if (!virBufferUse(&childrenBuf)) { + ret = 0; + goto cleanup; + } + + vcpus = virBitmapFormat(cachetune->vcpus); + + virBufferAsprintf(buf, "<cachetune vcpus='%s'>\n", vcpus); + virBufferAddBuffer(buf, &childrenBuf); + virBufferAddLit(buf, "</cachetune>\n"); + + ret = 0; + cleanup: + virBufferFreeAndReset(&childrenBuf); + VIR_FREE(vcpus); + return ret; +} + + static int virDomainCputuneDefFormat(virBufferPtr buf, virDomainDefPtr def) @@ -25545,6 +25791,9 @@ virDomainCputuneDefFormat(virBufferPtr buf, def->iothreadids[i]->iothread_id); } + for (i = 0; i < def->ncachetunes; i++) + virDomainCachetuneDefFormat(&childrenBuf, def->cachetunes[i]); + if (virBufferCheckError(&childrenBuf) < 0) return -1; diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index be38792c6942..bda83d72e7ed 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -56,6 +56,7 @@ # include "virperf.h" # include "virtypedparam.h" # include "virsavecookie.h" +# include "virresctrl.h" /* forward declarations of all device types, required by * virDomainDeviceDef @@ -2167,6 +2168,15 @@ struct _virDomainCputune { }; +typedef struct _virDomainCachetuneDef virDomainCachetuneDef; +typedef virDomainCachetuneDef *virDomainCachetuneDefPtr; + +struct _virDomainCachetuneDef { + virBitmapPtr vcpus; + virResctrlAllocPtr alloc; +}; + + typedef struct _virDomainVcpuDef virDomainVcpuDef; typedef virDomainVcpuDef *virDomainVcpuDefPtr; @@ -2294,6 +2304,17 @@ struct _virDomainDef { virDomainIOThreadIDDefPtr *iothreadids; virDomainCputune cputune; + /* + * Both cachetune as well as runtime allocation (the same structure, but + * updated) for all threads is kept here. It is specified here instead of + * specifying it in virDomainVcpuDef _deliberately_ because it needs to be + * set at once under one flock() and not for each thread separately. Also + * if one cachetune is specified for multiple vCPUs, it must be created as + * one due to limited number of concurrent cachetune settings (number of + * CLoS IDs) provided by hardware. + */ + virDomainCachetuneDefPtr *cachetunes; + size_t ncachetunes; virDomainNumaPtr numa; virDomainResourceDefPtr resource; diff --git a/src/util/virresctrl.c b/src/util/virresctrl.c index ac1b38436bb2..8753b1dca325 100644 --- a/src/util/virresctrl.c +++ b/src/util/virresctrl.c @@ -693,7 +693,7 @@ virResctrlAllocSetID(virResctrlAllocPtr alloc, { if (!id) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("Resctrl allocation id cannot be NULL")); + _("Resctrl allocation 'id' cannot be NULL")); return -1; } diff --git a/tests/genericxml2xmlindata/generic-cachetune-cdp.xml b/tests/genericxml2xmlindata/generic-cachetune-cdp.xml new file mode 100644 index 000000000000..1331aad06e54 --- /dev/null +++ b/tests/genericxml2xmlindata/generic-cachetune-cdp.xml @@ -0,0 +1,36 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219136</memory> + <currentMemory unit='KiB'>219136</currentMemory> + <vcpu placement='static'>2</vcpu> + <cputune> + <cachetune vcpus='0-1'> + <cache id='0' level='3' type='code' size='7680' unit='KiB'/> + <cache id='1' level='3' type='data' size='3840' unit='KiB'/> + </cachetune> + <cachetune vcpus='2'> + <cache id='1' level='3' type='code' size='6' unit='MiB'/> + </cachetune> + <cachetune vcpus='3'> + <cache id='1' level='3' type='data' size='6912' unit='KiB'/> + </cachetune> + </cputune> + <os> + <type arch='i686' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-i686</emulator> + <controller type='usb' index='0'/> + <controller type='ide' index='0'/> + <controller type='pci' index='0' model='pci-root'/> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <memballoon model='virtio'/> + </devices> +</domain> diff --git a/tests/genericxml2xmlindata/generic-cachetune-colliding-allocs.xml b/tests/genericxml2xmlindata/generic-cachetune-colliding-allocs.xml new file mode 100644 index 000000000000..994f8fcf2a4e --- /dev/null +++ b/tests/genericxml2xmlindata/generic-cachetune-colliding-allocs.xml @@ -0,0 +1,30 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219136</memory> + <currentMemory unit='KiB'>219136</currentMemory> + <vcpu placement='static'>2</vcpu> + <cputune> + <cachetune vcpus='0'> + <cache id='0' level='3' type='code' size='12' unit='KiB'/> + <cache id='0' level='3' type='code' size='18' unit='KiB'/> + </cachetune> + </cputune> + <os> + <type arch='i686' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-i686</emulator> + <controller type='usb' index='0'/> + <controller type='ide' index='0'/> + <controller type='pci' index='0' model='pci-root'/> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <memballoon model='virtio'/> + </devices> +</domain> diff --git a/tests/genericxml2xmlindata/generic-cachetune-colliding-tunes.xml b/tests/genericxml2xmlindata/generic-cachetune-colliding-tunes.xml new file mode 100644 index 000000000000..3d9db85b12d4 --- /dev/null +++ b/tests/genericxml2xmlindata/generic-cachetune-colliding-tunes.xml @@ -0,0 +1,32 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219136</memory> + <currentMemory unit='KiB'>219136</currentMemory> + <vcpu placement='static'>2</vcpu> + <cputune> + <cachetune vcpus='0'> + <cache id='0' level='3' type='code' size='12' unit='KiB'/> + </cachetune> + <cachetune vcpus='0'> + <cache id='0' level='3' type='data' size='18' unit='KiB'/> + </cachetune> + </cputune> + <os> + <type arch='i686' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-i686</emulator> + <controller type='usb' index='0'/> + <controller type='ide' index='0'/> + <controller type='pci' index='0' model='pci-root'/> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <memballoon model='virtio'/> + </devices> +</domain> diff --git a/tests/genericxml2xmlindata/generic-cachetune-colliding-types.xml b/tests/genericxml2xmlindata/generic-cachetune-colliding-types.xml new file mode 100644 index 000000000000..1a4e62cd17d3 --- /dev/null +++ b/tests/genericxml2xmlindata/generic-cachetune-colliding-types.xml @@ -0,0 +1,30 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219136</memory> + <currentMemory unit='KiB'>219136</currentMemory> + <vcpu placement='static'>2</vcpu> + <cputune> + <cachetune vcpus='0-1'> + <cache id='0' level='3' type='both' size='12' unit='KiB'/> + <cache id='0' level='3' type='code' size='6' unit='KiB'/> + </cachetune> + </cputune> + <os> + <type arch='i686' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-i686</emulator> + <controller type='usb' index='0'/> + <controller type='ide' index='0'/> + <controller type='pci' index='0' model='pci-root'/> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <memballoon model='virtio'/> + </devices> +</domain> diff --git a/tests/genericxml2xmlindata/generic-cachetune-small.xml b/tests/genericxml2xmlindata/generic-cachetune-small.xml new file mode 100644 index 000000000000..4b25490b3abb --- /dev/null +++ b/tests/genericxml2xmlindata/generic-cachetune-small.xml @@ -0,0 +1,29 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219136</memory> + <currentMemory unit='KiB'>219136</currentMemory> + <vcpu placement='static'>2</vcpu> + <cputune> + <cachetune vcpus='0-1'> + <cache id='0' level='3' type='both' size='768' unit='KiB'/> + </cachetune> + </cputune> + <os> + <type arch='i686' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-i686</emulator> + <controller type='usb' index='0'/> + <controller type='ide' index='0'/> + <controller type='pci' index='0' model='pci-root'/> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <memballoon model='virtio'/> + </devices> +</domain> diff --git a/tests/genericxml2xmlindata/generic-cachetune.xml b/tests/genericxml2xmlindata/generic-cachetune.xml new file mode 100644 index 000000000000..0051410aec32 --- /dev/null +++ b/tests/genericxml2xmlindata/generic-cachetune.xml @@ -0,0 +1,33 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219136</memory> + <currentMemory unit='KiB'>219136</currentMemory> + <vcpu placement='static'>2</vcpu> + <cputune> + <cachetune vcpus='0-1'> + <cache id='0' level='3' type='both' size='3' unit='MiB'/> + <cache id='1' level='3' type='both' size='3' unit='MiB'/> + </cachetune> + <cachetune vcpus='3'> + <cache id='0' level='3' type='both' size='3' unit='MiB'/> + </cachetune> + </cputune> + <os> + <type arch='i686' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-i686</emulator> + <controller type='usb' index='0'/> + <controller type='ide' index='0'/> + <controller type='pci' index='0' model='pci-root'/> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <memballoon model='virtio'/> + </devices> +</domain> diff --git a/tests/genericxml2xmltest.c b/tests/genericxml2xmltest.c index a0900d9db7e4..0f1a69fef892 100644 --- a/tests/genericxml2xmltest.c +++ b/tests/genericxml2xmltest.c @@ -130,6 +130,16 @@ mymain(void) DO_TEST_FULL("chardev-reconnect-invalid-mode", 0, false, TEST_COMPARE_DOM_XML2XML_RESULT_FAIL_PARSE); + DO_TEST("cachetune"); + DO_TEST("cachetune-small"); + DO_TEST("cachetune-cdp"); + DO_TEST_FULL("cachetune-colliding-allocs", false, true, + TEST_COMPARE_DOM_XML2XML_RESULT_FAIL_PARSE); + DO_TEST_FULL("cachetune-colliding-tunes", false, true, + TEST_COMPARE_DOM_XML2XML_RESULT_FAIL_PARSE); + DO_TEST_FULL("cachetune-colliding-types", false, true, + TEST_COMPARE_DOM_XML2XML_RESULT_FAIL_PARSE); + virObjectUnref(caps); virObjectUnref(xmlopt); -- 2.15.0

Short, sweet, and to the point. Looks like this patch does nothing, eh? :-)... Of course you realized that based on my patch 15 review. After reading the cover letter I was expecting a least a few sentences here, but nothing, sigh. On 11/13/2017 03:50 AM, Martin Kletzander wrote:
Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- docs/formatdomain.html.in | 24 ++ docs/schemas/domaincommon.rng | 32 +++ src/conf/domain_conf.c | 249 +++++++++++++++++++++ src/conf/domain_conf.h | 21 ++ src/util/virresctrl.c | 2 +- .../genericxml2xmlindata/generic-cachetune-cdp.xml | 36 +++ .../generic-cachetune-colliding-allocs.xml | 30 +++ .../generic-cachetune-colliding-tunes.xml | 32 +++ .../generic-cachetune-colliding-types.xml | 30 +++ .../generic-cachetune-small.xml | 29 +++ tests/genericxml2xmlindata/generic-cachetune.xml | 33 +++ tests/genericxml2xmltest.c | 10 + 12 files changed, 527 insertions(+), 1 deletion(-) create mode 100644 tests/genericxml2xmlindata/generic-cachetune-cdp.xml create mode 100644 tests/genericxml2xmlindata/generic-cachetune-colliding-allocs.xml create mode 100644 tests/genericxml2xmlindata/generic-cachetune-colliding-tunes.xml create mode 100644 tests/genericxml2xmlindata/generic-cachetune-colliding-types.xml create mode 100644 tests/genericxml2xmlindata/generic-cachetune-small.xml create mode 100644 tests/genericxml2xmlindata/generic-cachetune.xml
diff --git a/docs/formatdomain.html.in b/docs/formatdomain.html.in index 92e14a919aba..b9d7f53b31f9 100644 --- a/docs/formatdomain.html.in +++ b/docs/formatdomain.html.in @@ -689,6 +689,10 @@ <iothread_quota>-1</iothread_quota> <vcpusched vcpus='0-4,^3' scheduler='fifo' priority='1'/> <iothreadsched iothreads='2' scheduler='batch'/> + <cachetune vcpus='0-3'> + <cache id='0' level='3' type='both' size='3' unit='MiB'/> + <cache id='1' level='3' type='both' size='3' unit='MiB'/> + </cachetune> </cputune> ... </domain> @@ -834,6 +838,26 @@ <span class="since">Since 1.2.13</span> </dd>
+ <dt><code>cachetune</code></dt>
Perhaps this goes against norms, but I think that <span> should be placed on the "cachetune" above to signify it's for all of these. If some day later a new element or attribute is created it then gets it's one <span> for the since of that specific version.
+ <dd> + Optional <code>cachetune</code> element can control allocations for CPU + caches using the resctrlfs on the host. Whether or not is this supported + can be gathered from capabilities where some limitations like minimum + size and required granularity are reported as well. + Attribute <code>vcpus</code> specifies what vCPUs this allocation
The required attribute... s/what/to which/
+ applies to. One vCPU cannot have multiple <code>cachetune</code> + allocations.
A vCPU can only be a member of one <code>cachetune</code> element allocation.
+ For now there is only one supported + sub-element, <code>cache</code>. That element must have
Each <code>vcputune</code> element must have at least one <code>cache</code> subelement. The <code>cache</code> subelement must have
+ attributes <code>level</code> and <code>id</code> - host's cache level/id + to allocate from, <code>type</code> - whether to + allocate <code>code</code>, <code>data</code> or <code>both</code> + and <code>size</code> - size of the region to allocate. Size is by + default in bytes, but optional attribute <code>unit</code> can be used + to scale the size appropriately. Each <code>cachetune</code> can have + multiple <code>cache</code> elements. <span class="since">Since + 3.10.0</span>
Also I like it better when the subelements are in some sort of list format as it's easier to read than a long sentence. So picking up from where I left off before: the following attributes: <dl> <dt><code>level</code></dt> <dd>Host cache level from which to allocate.</dd> <dt><code>id</code></dt> <dd>Host cache id from which to allocate.</dd> <dt><code>type</code></dt> <dd>Whether to allocate <code>code</code>, <code>data</code>, or <code>both</code> regions.</dd> <dt><code>size</code></dt> <dd>The size of the region to allocate. The value by default is in bytes, but the <code>unit</code> attribute can be used to scale the value.</dd> <dt><code>unit</code></dt> <dd>Scaled memory value such as KiB, MiB, GiB, or TiB described in the <code>memory</code> element for <a href="#elementsMemoryAllocation">Memory Allocation</a>.</dd> </dl> BTW: Whether you expound upon what data, code, and both are is up to you. I have a good idea what that means, but it's not necessarily well described as to the usefulness regarding why to select one or the other.
+ + </dd> </dl>
diff --git a/docs/schemas/domaincommon.rng b/docs/schemas/domaincommon.rng index 9cec1a063724..5d90247799ad 100644 --- a/docs/schemas/domaincommon.rng +++ b/docs/schemas/domaincommon.rng @@ -900,6 +900,38 @@ <ref name="schedparam"/> </element> </zeroOrMore> + <zeroOrMore> + <element name="cachetune"> + <attribute name="vcpus"> + <ref name='cpuset'/> + </attribute> + <oneOrMore> + <element name="cache"> + <attribute name="id"> + <ref name='unsignedInt'/> + </attribute> + <attribute name="level"> + <ref name='unsignedInt'/> + </attribute> + <attribute name="type"> + <choice> + <value>both</value> + <value>code</value> + <value>data</value> + </choice> + </attribute> + <attribute name="size"> + <ref name='unsignedLong'/> + </attribute> + <optional> + <attribute name='unit'> + <ref name='unit'/> + </attribute> + </optional> + </element> + </oneOrMore> + </element> + </zeroOrMore> </interleave> </element> </define> diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 400e900325f2..bf9e61efc8d2 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -2848,6 +2848,17 @@ virDomainLoaderDefFree(virDomainLoaderDefPtr loader) VIR_FREE(loader); }
+static void +virDomainCachetuneDefFree(virDomainCachetuneDefPtr cachetune) +{ + if (!cachetune) + return; + + virObjectUnref(cachetune->alloc); + virBitmapFree(cachetune->vcpus); + VIR_FREE(cachetune); +} + void virDomainDefFree(virDomainDefPtr def) { size_t i; @@ -3020,6 +3031,10 @@ void virDomainDefFree(virDomainDefPtr def) virDomainShmemDefFree(def->shmems[i]); VIR_FREE(def->shmems);
+ for (i = 0; i < def->ncachetunes; i++) + virDomainCachetuneDefFree(def->cachetunes[i]); + VIR_FREE(def->cachetunes); + VIR_FREE(def->keywrap);
if (def->namespaceData && def->ns.free) @@ -18080,6 +18095,163 @@ virDomainDefParseBootOptions(virDomainDefPtr def, }
+static int +virDomainCachetuneDefParseCache(xmlXPathContextPtr ctxt, + xmlNodePtr node, + virResctrlAllocPtr *alloc) +{ + xmlNodePtr oldnode = ctxt->node; + unsigned int level; + unsigned int cache; + int type; + unsigned long long size; + char *tmp = NULL; + int ret = -1; + + ctxt->node = node; + + tmp = virXMLPropString(node, "id"); + if (!tmp) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("Missing attribute id for cachetune/cache"));
'id' (and only because later on you added '' around id in a different message ;-) Just cachetune?? (consistency w/ other messages - IDC which way, but similar point in this function and the folowing one)
+ goto cleanup; + } + if (virStrToLong_uip(tmp, NULL, 10, &cache) < 0) { + virReportError(VIR_ERR_XML_ERROR, + _("Invalid attribute id '%s' for cachetune/cache"),
Maybe it's just me, but the message just doesn't flow well, consider: "Invalid cachetune attribute 'id' value '%s'" It's not all that important though, but could be used in subsequent messages too.
+ tmp); + goto cleanup; + } + VIR_FREE(tmp); + + tmp = virXMLPropString(node, "level"); + if (!tmp) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("Missing attribute level for cachetune/cache"));
'level'
+ goto cleanup; + } + if (virStrToLong_uip(tmp, NULL, 10, &level) < 0) { + virReportError(VIR_ERR_XML_ERROR, + _("Invalid attribute level '%s' for cachetune/cache"), + tmp); + goto cleanup; + } + VIR_FREE(tmp); + + tmp = virXMLPropString(node, "type"); + if (!tmp) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("Missing attribute type for cachetune/cache"));
'type'
+ goto cleanup; + } + type = virCacheTypeFromString(tmp); + if (type < 0) { + virReportError(VIR_ERR_XML_ERROR, + _("Invalid attribute type '%s' for cachetune"), + tmp); + goto cleanup; + } + VIR_FREE(tmp); + + if (virDomainParseScaledValue("./@size", "./@unit", + ctxt, &size, 1024, + ULLONG_MAX, true) < 0) + goto cleanup; + + if (virResctrlAllocSetSize(alloc, level, type, cache, size) < 0) + goto cleanup; + + ret = 0; + cleanup: + ctxt->node = oldnode; + VIR_FREE(tmp); + return ret; +} +
Two blank lines.
+static int +virDomainCachetuneDefParse(virDomainDefPtr def, + xmlXPathContextPtr ctxt, + xmlNodePtr node) +{ + xmlNodePtr oldnode = ctxt->node; + xmlNodePtr *nodes = NULL; + virBitmapPtr vcpus = NULL; + virResctrlAllocPtr alloc = NULL; + virDomainCachetuneDefPtr tmp_cachetune = NULL; + char *tmp = NULL; + char *vcpus_str = NULL; + ssize_t i = 0; + int n; + int ret = -1; + + ctxt->node = node; + + if (VIR_ALLOC(tmp_cachetune) < 0) + goto cleanup; + + vcpus_str = virXMLPropString(node, "vcpus"); + if (!vcpus_str) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("Missing attribute vcpus for cachetune"));
Here again w/ the cachetune/cache
+ goto cleanup; + } + if (virBitmapParse(vcpus_str, &vcpus, VIR_DOMAIN_CPUMASK_LEN) < 0) { + virReportError(VIR_ERR_XML_ERROR, + _("Invalid attribute vcpus '%s' for cachetune"), + vcpus_str); + goto cleanup; + } + + if ((n = virXPathNodeSet("./cache", ctxt, &nodes)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("cannot extract cachetune/cache nodes")); + goto cleanup; + } + + for (i = 0; i < n; i++) { + if (virDomainCachetuneDefParseCache(ctxt, nodes[i], &alloc) < 0) + goto cleanup; + } + + if (!alloc) { + ret = 0; + goto cleanup; + } + + virBitmapShrink(vcpus, def->maxvcpus); + + for (i = 0; i < def->ncachetunes; i++) { + if (virBitmapOverlaps(def->cachetunes[i]->vcpus, vcpus)) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("Overlapping vcpus in cachetunes")); + goto cleanup; + } + } + + if (virResctrlAllocSetID(alloc, vcpus_str) < 0) + goto cleanup; + + tmp_cachetune->vcpus = vcpus; + tmp_cachetune->alloc = alloc; + vcpus = NULL; + alloc = NULL; + + if (VIR_APPEND_ELEMENT(def->cachetunes, def->ncachetunes, tmp_cachetune) < 0) + goto cleanup; + + ret = 0; + cleanup: + ctxt->node = oldnode; + virDomainCachetuneDefFree(tmp_cachetune); + virObjectUnref(alloc); + virBitmapFree(vcpus); + VIR_FREE(vcpus_str); + VIR_FREE(nodes); + VIR_FREE(tmp); + return ret; +} + + static virDomainDefPtr virDomainDefParseXML(xmlDocPtr xml, xmlNodePtr root, @@ -18632,6 +18804,18 @@ virDomainDefParseXML(xmlDocPtr xml, } VIR_FREE(nodes);
+ if ((n = virXPathNodeSet("./cputune/cachetune", ctxt, &nodes)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("cannot extract cachetune nodes")); + goto error; + } + + for (i = 0; i < n; i++) { + if (virDomainCachetuneDefParse(def, ctxt, nodes[i]) < 0) + goto error; + } + VIR_FREE(nodes); + if (virCPUDefParseXML(ctxt, "./cpu[1]", VIR_CPU_TYPE_GUEST, &def->cpu) < 0) goto error;
@@ -25444,6 +25628,68 @@ virDomainSchedulerFormat(virBufferPtr buf, }
+struct virCachetuneHelperData { + virBufferPtr buf; + size_t vcpu_id; +}; + +static int +virDomainCachetuneDefFormatHelper(unsigned int level, + virCacheType type, + unsigned int cache, + unsigned long long size, + void *opaque) +{ + const char *unit; + virBufferPtr buf = opaque; + unsigned long long short_size = virPrettySize(size, &unit); + + virBufferAsprintf(buf, + "<cache id='%u' level='%u' type='%s' " + "size='%llu' unit='%s'/>\n", + cache, level, virCacheTypeToString(type), + short_size, unit); + + return 0; +} + + +static int +virDomainCachetuneDefFormat(virBufferPtr buf, + virDomainCachetuneDefPtr cachetune) +{ + virBuffer childrenBuf = VIR_BUFFER_INITIALIZER; + char *vcpus = NULL; + int ret = -1; + + virBufferSetChildIndent(&childrenBuf, buf); + virResctrlAllocForeachSize(cachetune->alloc, + virDomainCachetuneDefFormatHelper, + &childrenBuf); + + + if (virBufferCheckError(&childrenBuf) < 0) + goto cleanup; + + if (!virBufferUse(&childrenBuf)) { + ret = 0; + goto cleanup; + } + + vcpus = virBitmapFormat(cachetune->vcpus); + + virBufferAsprintf(buf, "<cachetune vcpus='%s'>\n", vcpus); + virBufferAddBuffer(buf, &childrenBuf); + virBufferAddLit(buf, "</cachetune>\n"); + + ret = 0; + cleanup: + virBufferFreeAndReset(&childrenBuf); + VIR_FREE(vcpus); + return ret; +} + +
The brevity of the format is something all formatting functions should aspire to become!
static int virDomainCputuneDefFormat(virBufferPtr buf, virDomainDefPtr def) @@ -25545,6 +25791,9 @@ virDomainCputuneDefFormat(virBufferPtr buf, def->iothreadids[i]->iothread_id); }
+ for (i = 0; i < def->ncachetunes; i++) + virDomainCachetuneDefFormat(&childrenBuf, def->cachetunes[i]); + if (virBufferCheckError(&childrenBuf) < 0) return -1;
diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index be38792c6942..bda83d72e7ed 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -56,6 +56,7 @@ # include "virperf.h" # include "virtypedparam.h" # include "virsavecookie.h" +# include "virresctrl.h"
/* forward declarations of all device types, required by * virDomainDeviceDef @@ -2167,6 +2168,15 @@ struct _virDomainCputune { };
+typedef struct _virDomainCachetuneDef virDomainCachetuneDef; +typedef virDomainCachetuneDef *virDomainCachetuneDefPtr; + +struct _virDomainCachetuneDef { + virBitmapPtr vcpus; + virResctrlAllocPtr alloc; +}; + + typedef struct _virDomainVcpuDef virDomainVcpuDef; typedef virDomainVcpuDef *virDomainVcpuDefPtr;
@@ -2294,6 +2304,17 @@ struct _virDomainDef { virDomainIOThreadIDDefPtr *iothreadids;
virDomainCputune cputune; + /* + * Both cachetune as well as runtime allocation (the same structure, but + * updated) for all threads is kept here. It is specified here instead of + * specifying it in virDomainVcpuDef _deliberately_ because it needs to be + * set at once under one flock() and not for each thread separately. Also + * if one cachetune is specified for multiple vCPUs, it must be created as + * one due to limited number of concurrent cachetune settings (number of + * CLoS IDs) provided by hardware. + */
Well that's lengthy! Still that last sentence makes me wonder if that "limited number" is known or is it random and thus if enough were created there would be random failures...
+ virDomainCachetuneDefPtr *cachetunes; + size_t ncachetunes;
virDomainNumaPtr numa; virDomainResourceDefPtr resource; diff --git a/src/util/virresctrl.c b/src/util/virresctrl.c index ac1b38436bb2..8753b1dca325 100644 --- a/src/util/virresctrl.c +++ b/src/util/virresctrl.c @@ -693,7 +693,7 @@ virResctrlAllocSetID(virResctrlAllocPtr alloc, { if (!id) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("Resctrl allocation id cannot be NULL")); + _("Resctrl allocation 'id' cannot be NULL"));
This probably should go in some earlier patch (if it's not already pushed ;-)). I think the fixup's are essentially simple. I'm fine with the naming scheme - although maybe someone else who has been thinking about this longer, but didn't jump in to review would have a different opinion ;-) Reviewed-by: John Ferlan <jferlan@redhat.com> John
return -1; }
[...]

On Mon, Nov 13, 2017 at 09:50:32AM +0100, Martin Kletzander wrote:
Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- docs/formatdomain.html.in | 24 ++ docs/schemas/domaincommon.rng | 32 +++ src/conf/domain_conf.c | 249 +++++++++++++++++++++ src/conf/domain_conf.h | 21 ++ src/util/virresctrl.c | 2 +- .../genericxml2xmlindata/generic-cachetune-cdp.xml | 36 +++ .../generic-cachetune-colliding-allocs.xml | 30 +++ .../generic-cachetune-colliding-tunes.xml | 32 +++ .../generic-cachetune-colliding-types.xml | 30 +++ .../generic-cachetune-small.xml | 29 +++ tests/genericxml2xmlindata/generic-cachetune.xml | 33 +++ tests/genericxml2xmltest.c | 10 + 12 files changed, 527 insertions(+), 1 deletion(-) create mode 100644 tests/genericxml2xmlindata/generic-cachetune-cdp.xml create mode 100644 tests/genericxml2xmlindata/generic-cachetune-colliding-allocs.xml create mode 100644 tests/genericxml2xmlindata/generic-cachetune-colliding-tunes.xml create mode 100644 tests/genericxml2xmlindata/generic-cachetune-colliding-types.xml create mode 100644 tests/genericxml2xmlindata/generic-cachetune-small.xml create mode 100644 tests/genericxml2xmlindata/generic-cachetune.xml
[...]
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 400e900325f2..bf9e61efc8d2 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c
[...]
+static int +virDomainCachetuneDefParse(virDomainDefPtr def, + xmlXPathContextPtr ctxt, + xmlNodePtr node) +{ + xmlNodePtr oldnode = ctxt->node; + xmlNodePtr *nodes = NULL; + virBitmapPtr vcpus = NULL; + virResctrlAllocPtr alloc = NULL; + virDomainCachetuneDefPtr tmp_cachetune = NULL; + char *tmp = NULL; + char *vcpus_str = NULL; + ssize_t i = 0; + int n; + int ret = -1; + + ctxt->node = node; + + if (VIR_ALLOC(tmp_cachetune) < 0) + goto cleanup; + + vcpus_str = virXMLPropString(node, "vcpus"); + if (!vcpus_str) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("Missing attribute vcpus for cachetune")); + goto cleanup; + } + if (virBitmapParse(vcpus_str, &vcpus, VIR_DOMAIN_CPUMASK_LEN) < 0) { + virReportError(VIR_ERR_XML_ERROR, + _("Invalid attribute vcpus '%s' for cachetune"), + vcpus_str); + goto cleanup; + } + + if ((n = virXPathNodeSet("./cache", ctxt, &nodes)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("cannot extract cachetune/cache nodes")); + goto cleanup; + } + + for (i = 0; i < n; i++) { + if (virDomainCachetuneDefParseCache(ctxt, nodes[i], &alloc) < 0) + goto cleanup; + } + + if (!alloc) { + ret = 0; + goto cleanup; + } + + virBitmapShrink(vcpus, def->maxvcpus); + + for (i = 0; i < def->ncachetunes; i++) { + if (virBitmapOverlaps(def->cachetunes[i]->vcpus, vcpus)) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("Overlapping vcpus in cachetunes")); + goto cleanup; + } + } + + if (virResctrlAllocSetID(alloc, vcpus_str) < 0) + goto cleanup; + + tmp_cachetune->vcpus = vcpus; + tmp_cachetune->alloc = alloc; + vcpus = NULL; + alloc = NULL;
VIR_STEAL_PTR()
+ + if (VIR_APPEND_ELEMENT(def->cachetunes, def->ncachetunes, tmp_cachetune) < 0) + goto cleanup; + + ret = 0; + cleanup: + ctxt->node = oldnode; + virDomainCachetuneDefFree(tmp_cachetune); + virObjectUnref(alloc); + virBitmapFree(vcpus); + VIR_FREE(vcpus_str); + VIR_FREE(nodes); + VIR_FREE(tmp); + return ret; +} + + static virDomainDefPtr virDomainDefParseXML(xmlDocPtr xml, xmlNodePtr root, @@ -18632,6 +18804,18 @@ virDomainDefParseXML(xmlDocPtr xml, } VIR_FREE(nodes);
+ if ((n = virXPathNodeSet("./cputune/cachetune", ctxt, &nodes)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("cannot extract cachetune nodes")); + goto error; + } + + for (i = 0; i < n; i++) { + if (virDomainCachetuneDefParse(def, ctxt, nodes[i]) < 0) + goto error; + } + VIR_FREE(nodes); + if (virCPUDefParseXML(ctxt, "./cpu[1]", VIR_CPU_TYPE_GUEST, &def->cpu) < 0) goto error;
@@ -25444,6 +25628,68 @@ virDomainSchedulerFormat(virBufferPtr buf, }
+struct virCachetuneHelperData { + virBufferPtr buf; + size_t vcpu_id; +}; + +static int +virDomainCachetuneDefFormatHelper(unsigned int level, + virCacheType type, + unsigned int cache, + unsigned long long size, + void *opaque)
Wrong indentation.
+{ + const char *unit; + virBufferPtr buf = opaque; + unsigned long long short_size = virPrettySize(size, &unit); + + virBufferAsprintf(buf, + "<cache id='%u' level='%u' type='%s' " + "size='%llu' unit='%s'/>\n", + cache, level, virCacheTypeToString(type), + short_size, unit); + + return 0; +}
Pavel

This patch modifies some not yet used test data so that the adding a test using this data is a clean patch and not an addition of huge file with some adjustments in small files that will be hidden in the middle of that commit. These changes include: - Add system dir in vircaps2xmldata/linux-caches Back when data for systems with resctrl support were added they had the /sys/fs/system directory put into a system/ subdir of the test and /sys/fs/resctrl in a resctrl/ subdir of that test. However, if we also want a negative test for the resctrl (requesting allocation on a system that does not support resctrl), we need one a test case with any sensible (with cache info) system/ subdir and no resctrl/ one. Easiest way is to add a system -> . symlink into existing test case. - Change linux-resctrl's schemata for default group That way we can fit some allocation in. - Remove one cache from resctrl-skx's schemata and make some room for allocations That system already has only one cache, so that file was wrong anyway. We have a version with 2 caches already (linux-resctrl-skx-twocaches), so this will also add variety to future tests. - Add some empty allocation for resctrl-skx Just to have slightly more coverage and variety. We can be sure nothing bad happens if such allocation exists in case we have that in the tests. Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- tests/vircaps2xmldata/linux-caches/system | 1 + tests/vircaps2xmldata/linux-resctrl-skx/resctrl/empty/schemata | 0 tests/vircaps2xmldata/linux-resctrl-skx/resctrl/schemata | 2 +- tests/vircaps2xmldata/linux-resctrl/resctrl/schemata | 2 +- 4 files changed, 3 insertions(+), 2 deletions(-) create mode 120000 tests/vircaps2xmldata/linux-caches/system create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx/resctrl/empty/schemata diff --git a/tests/vircaps2xmldata/linux-caches/system b/tests/vircaps2xmldata/linux-caches/system new file mode 120000 index 000000000000..945c9b46d684 --- /dev/null +++ b/tests/vircaps2xmldata/linux-caches/system @@ -0,0 +1 @@ +. \ No newline at end of file diff --git a/tests/vircaps2xmldata/linux-resctrl-skx/resctrl/empty/schemata b/tests/vircaps2xmldata/linux-resctrl-skx/resctrl/empty/schemata new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/vircaps2xmldata/linux-resctrl-skx/resctrl/schemata b/tests/vircaps2xmldata/linux-resctrl-skx/resctrl/schemata index 4a4913735386..191081f1db47 100644 --- a/tests/vircaps2xmldata/linux-resctrl-skx/resctrl/schemata +++ b/tests/vircaps2xmldata/linux-resctrl-skx/resctrl/schemata @@ -1 +1 @@ -L3:0=7ff;1=7ff +L3:0=0f0 diff --git a/tests/vircaps2xmldata/linux-resctrl/resctrl/schemata b/tests/vircaps2xmldata/linux-resctrl/resctrl/schemata index 9b47d25fc72e..c1a765f1e0a7 100644 --- a/tests/vircaps2xmldata/linux-resctrl/resctrl/schemata +++ b/tests/vircaps2xmldata/linux-resctrl/resctrl/schemata @@ -1 +1 @@ -L3:0=1ffff;1=1ffff +L3:0=1ff00;1=1ff0f -- 2.15.0

On 11/13/2017 03:50 AM, Martin Kletzander wrote:
This patch modifies some not yet used test data so that the adding a test using this data is a clean patch and not an addition of huge file with some adjustments in small files that will be hidden in the middle of that commit. These changes include:
- Add system dir in vircaps2xmldata/linux-caches
Back when data for systems with resctrl support were added they had the /sys/fs/system directory put into a system/ subdir of the test and /sys/fs/resctrl in a resctrl/ subdir of that test. However, if we also want a negative test for the resctrl (requesting allocation on a system that does not support resctrl), we need one a test case with any sensible (with cache info) system/ subdir and no resctrl/ one. Easiest way is to add a system -> . symlink into existing test case.
- Change linux-resctrl's schemata for default group
That way we can fit some allocation in.
- Remove one cache from resctrl-skx's schemata and make some room for allocations
That system already has only one cache, so that file was wrong anyway. We have a version with 2 caches already (linux-resctrl-skx-twocaches), so this will also add variety to future tests.
- Add some empty allocation for resctrl-skx
Just to have slightly more coverage and variety. We can be sure nothing bad happens if such allocation exists in case we have that in the tests.
Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- tests/vircaps2xmldata/linux-caches/system | 1 + tests/vircaps2xmldata/linux-resctrl-skx/resctrl/empty/schemata | 0 tests/vircaps2xmldata/linux-resctrl-skx/resctrl/schemata | 2 +- tests/vircaps2xmldata/linux-resctrl/resctrl/schemata | 2 +- 4 files changed, 3 insertions(+), 2 deletions(-) create mode 120000 tests/vircaps2xmldata/linux-caches/system create mode 100644 tests/vircaps2xmldata/linux-resctrl-skx/resctrl/empty/schemata
And an unbelievably long commit message for what looks like some really minor changes ;-) I'm glad you know what the schemata entries mean, because to the untrained eye - they're just bits of data with no meaning. Reviewed-by: John Ferlan <jferlan@redhat.com> John

This test has 2 different uses: 1) DO_TEST_FREE initializes capabilities from vircaps2xmldata (since it exists there already) and then requests list of free bitmaps (all unallocated space) from virresctrl.c 2) DO_TEST_ALLOC takes capabilities from vircaps2xmldata, and uses resctrl info to request an allocation from virresctrl.c for a VM from genericxml2xmlindata. Desirable outputs are saved in virresctrldata. Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- tests/Makefile.am | 8 +- .../resctrl--cachetune/vcpus-0-1.alloc | 1 + .../resctrl--cachetune/vcpus-3.alloc | 1 + .../resctrl-cdp--cachetune-cdp/vcpus-0-1.alloc | 2 + .../resctrl-cdp--cachetune-cdp/vcpus-2.alloc | 1 + .../resctrl-cdp--cachetune-cdp/vcpus-3.alloc | 1 + tests/virresctrldata/resctrl-cdp.schemata | 2 + .../virresctrldata/resctrl-skx-twocaches.schemata | 1 + tests/virresctrldata/resctrl-skx.schemata | 1 + tests/virresctrldata/resctrl.schemata | 1 + tests/virresctrltest.c | 277 +++++++++++++++++++++ 11 files changed, 295 insertions(+), 1 deletion(-) create mode 100644 tests/virresctrldata/resctrl--cachetune/vcpus-0-1.alloc create mode 100644 tests/virresctrldata/resctrl--cachetune/vcpus-3.alloc create mode 100644 tests/virresctrldata/resctrl-cdp--cachetune-cdp/vcpus-0-1.alloc create mode 100644 tests/virresctrldata/resctrl-cdp--cachetune-cdp/vcpus-2.alloc create mode 100644 tests/virresctrldata/resctrl-cdp--cachetune-cdp/vcpus-3.alloc create mode 100644 tests/virresctrldata/resctrl-cdp.schemata create mode 100644 tests/virresctrldata/resctrl-skx-twocaches.schemata create mode 100644 tests/virresctrldata/resctrl-skx.schemata create mode 100644 tests/virresctrldata/resctrl.schemata create mode 100644 tests/virresctrltest.c diff --git a/tests/Makefile.am b/tests/Makefile.am index effa259e3a45..04695c0ad250 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -231,6 +231,7 @@ if WITH_LINUX test_programs += fchosttest test_programs += scsihosttest test_programs += vircaps2xmltest +test_programs += virresctrltest test_libraries += virusbmock.la \ virnetdevbandwidthmock.la \ virnumamock.la \ @@ -1172,8 +1173,13 @@ virnumamock_la_CFLAGS = $(AM_CFLAGS) virnumamock_la_LDFLAGS = $(MOCKLIBS_LDFLAGS) virnumamock_la_LIBADD = $(MOCKLIBS_LIBS) +virresctrltest_SOURCES = \ + virresctrltest.c testutils.h testutils.c virfilewrapper.h virfilewrapper.c +virresctrltest_LDADD = $(LDADDS) + else ! WITH_LINUX -EXTRA_DIST += vircaps2xmltest.c virnumamock.c virfilewrapper.c virfilewrapper.h +EXTRA_DIST += vircaps2xmltest.c virnumamock.c virfilewrapper.c \ + virfilewrapper.h virresctrltest.c endif ! WITH_LINUX if WITH_NSS diff --git a/tests/virresctrldata/resctrl--cachetune/vcpus-0-1.alloc b/tests/virresctrldata/resctrl--cachetune/vcpus-0-1.alloc new file mode 100644 index 000000000000..6f48dd99d5a1 --- /dev/null +++ b/tests/virresctrldata/resctrl--cachetune/vcpus-0-1.alloc @@ -0,0 +1 @@ +L3:0=0000f;1=000f0 diff --git a/tests/virresctrldata/resctrl--cachetune/vcpus-3.alloc b/tests/virresctrldata/resctrl--cachetune/vcpus-3.alloc new file mode 100644 index 000000000000..b47a36fecc8a --- /dev/null +++ b/tests/virresctrldata/resctrl--cachetune/vcpus-3.alloc @@ -0,0 +1 @@ +L3:0=000f0 diff --git a/tests/virresctrldata/resctrl-cdp--cachetune-cdp/vcpus-0-1.alloc b/tests/virresctrldata/resctrl-cdp--cachetune-cdp/vcpus-0-1.alloc new file mode 100644 index 000000000000..44886a2cfe38 --- /dev/null +++ b/tests/virresctrldata/resctrl-cdp--cachetune-cdp/vcpus-0-1.alloc @@ -0,0 +1,2 @@ +L3CODE:0=00ffc +L3DATA:1=0001f diff --git a/tests/virresctrldata/resctrl-cdp--cachetune-cdp/vcpus-2.alloc b/tests/virresctrldata/resctrl-cdp--cachetune-cdp/vcpus-2.alloc new file mode 100644 index 000000000000..4225b0a36167 --- /dev/null +++ b/tests/virresctrldata/resctrl-cdp--cachetune-cdp/vcpus-2.alloc @@ -0,0 +1 @@ +L3CODE:1=0ff00 diff --git a/tests/virresctrldata/resctrl-cdp--cachetune-cdp/vcpus-3.alloc b/tests/virresctrldata/resctrl-cdp--cachetune-cdp/vcpus-3.alloc new file mode 100644 index 000000000000..10be6844bd38 --- /dev/null +++ b/tests/virresctrldata/resctrl-cdp--cachetune-cdp/vcpus-3.alloc @@ -0,0 +1 @@ +L3DATA:1=03fe0 diff --git a/tests/virresctrldata/resctrl-cdp.schemata b/tests/virresctrldata/resctrl-cdp.schemata new file mode 100644 index 000000000000..2897e2afa603 --- /dev/null +++ b/tests/virresctrldata/resctrl-cdp.schemata @@ -0,0 +1,2 @@ +L3CODE:0=00ffc;1=0ff00 +L3DATA:0=00000;1=03fff diff --git a/tests/virresctrldata/resctrl-skx-twocaches.schemata b/tests/virresctrldata/resctrl-skx-twocaches.schemata new file mode 100644 index 000000000000..86b3801a04c2 --- /dev/null +++ b/tests/virresctrldata/resctrl-skx-twocaches.schemata @@ -0,0 +1 @@ +L3:0=001;1=400 diff --git a/tests/virresctrldata/resctrl-skx.schemata b/tests/virresctrldata/resctrl-skx.schemata new file mode 100644 index 000000000000..5e8b0d636277 --- /dev/null +++ b/tests/virresctrldata/resctrl-skx.schemata @@ -0,0 +1 @@ +L3:0=70f diff --git a/tests/virresctrldata/resctrl.schemata b/tests/virresctrldata/resctrl.schemata new file mode 100644 index 000000000000..fa980e58c9dd --- /dev/null +++ b/tests/virresctrldata/resctrl.schemata @@ -0,0 +1 @@ +L3:0=000ff;1=000f0 diff --git a/tests/virresctrltest.c b/tests/virresctrltest.c new file mode 100644 index 000000000000..e84d9ff94299 --- /dev/null +++ b/tests/virresctrltest.c @@ -0,0 +1,277 @@ +#include <config.h> +#include <stdlib.h> + +#include "testutils.h" +#include "virfilewrapper.h" +#include "virresctrlpriv.h" + + +#define VIR_FROM_THIS VIR_FROM_NONE + +struct virResctrlData { + const char *filename; + bool fail; + const char *test_name; +}; + + +static int +test_virResctrlGetFree(const void *opaque) +{ + struct virResctrlData *data = (struct virResctrlData *) opaque; + char *system_dir = NULL; + char *resctrl_dir = NULL; + int ret = -1; + virResctrlAllocPtr alloc = NULL; + char *schemata_str = NULL; + char *schemata_file; + virCapsPtr caps = NULL; + + if (virAsprintf(&system_dir, "%s/vircaps2xmldata/linux-%s/system", + abs_srcdir, data->filename) < 0) + goto cleanup; + + if (virAsprintf(&resctrl_dir, "%s/vircaps2xmldata/linux-%s/resctrl", + abs_srcdir, data->filename) < 0) + goto cleanup; + + if (virAsprintf(&schemata_file, "%s/virresctrldata/%s.schemata", + abs_srcdir, data->filename) < 0) + goto cleanup; + + virFileWrapperAddPrefix("/sys/devices/system", system_dir); + virFileWrapperAddPrefix("/sys/fs/resctrl", resctrl_dir); + + caps = virCapabilitiesNew(VIR_ARCH_X86_64, false, false); + if (!caps || virCapabilitiesInitCaches(caps) < 0) { + fprintf(stderr, "Could not initialize capabilities"); + goto cleanup; + } + + alloc = virResctrlAllocGetFree(caps->host.resctrl); + + virFileWrapperClearPrefixes(); + + if (!alloc) { + if (data->fail) + ret = 0; + goto cleanup; + } else if (data->fail) { + VIR_TEST_DEBUG("Error expected but there wasn't any.\n"); + ret = -1; + goto cleanup; + } + + schemata_str = virResctrlAllocFormat(alloc); + + if (virTestCompareToFile(schemata_str, schemata_file) < 0) + goto cleanup; + + ret = 0; + cleanup: + virObjectUnref(caps); + virObjectUnref(alloc); + VIR_FREE(system_dir); + VIR_FREE(resctrl_dir); + VIR_FREE(schemata_str); + VIR_FREE(schemata_file); + return ret; +} + +static int +test_virResctrlGetAlloc(const void *opaque) +{ + struct virResctrlData *data = (struct virResctrlData *) opaque; + char *system_dir = NULL; + char *resctrl_dir = NULL; + int ret = -1; + int rv = 0; + virResctrlAllocPtr alloc = NULL; + char *schemata_str = NULL; + char *schemata_file = NULL; + char *domain_file = NULL; + char *vcpus = NULL; + virCapsPtr caps = NULL; + virDomainDefPtr def = NULL; + size_t i = 0; + virDomainXMLOptionPtr xmlopt = NULL; + void *save_ptr = NULL; + + if (virAsprintf(&system_dir, "%s/vircaps2xmldata/linux-%s/system", + abs_srcdir, data->filename) < 0) + goto cleanup; + + if (virAsprintf(&resctrl_dir, "%s/vircaps2xmldata/linux-%s/resctrl", + abs_srcdir, data->filename) < 0) + goto cleanup; + + if (virAsprintf(&domain_file, "%s/genericxml2xmlindata/generic-%s.xml", + abs_srcdir, data->test_name) < 0) + goto cleanup; + + virFileWrapperAddPrefix("/sys/devices/system", system_dir); + virFileWrapperAddPrefix("/sys/fs/resctrl", resctrl_dir); + + caps = virTestGenericCapsInit(); + if (!caps || virCapabilitiesInitCaches(caps) < 0) { + fprintf(stderr, "Could not initialize capabilities"); + goto cleanup; + } + + xmlopt = virTestGenericDomainXMLConfInit(); + if (!xmlopt) + goto cleanup; + + def = virDomainDefParseFile(domain_file, caps, xmlopt, NULL, + VIR_DOMAIN_DEF_PARSE_INACTIVE); + if (!def) + goto cleanup; + + for (i = 0; i < def->ncachetunes; i++) { + if (virResctrlAllocMasksAssign(caps->host.resctrl, + def->cachetunes[i]->alloc, + &save_ptr) < 0) { + rv = -1; + break; + } + } + + virFileWrapperClearPrefixes(); + + if (rv < 0) { + if (data->fail) + ret = 0; + goto cleanup; + } else if (data->fail) { + VIR_TEST_DEBUG("Error expected but there wasn't any.\n"); + goto cleanup; + } + + for (i = 0; i < def->ncachetunes; i++) { + VIR_FREE(vcpus); + vcpus = virBitmapFormat(def->cachetunes[i]->vcpus); + + if (!vcpus) + goto cleanup; + + VIR_FREE(schemata_file); + VIR_FREE(schemata_str); + + if (virAsprintf(&schemata_file, "%s/virresctrldata/%s--%s/vcpus-%s.alloc", + abs_srcdir, data->filename, data->test_name, vcpus) < 0) + goto cleanup; + + schemata_str = virResctrlAllocFormat(def->cachetunes[i]->alloc); + if (!schemata_str) + goto cleanup; + + if (virTestCompareToFile(schemata_str, schemata_file) < 0) + rv = -1; + } + + ret = rv; + cleanup: + virObjectUnref(save_ptr); + virObjectUnref(caps); + virObjectUnref(alloc); + virObjectUnref(xmlopt); + VIR_FREE(system_dir); + VIR_FREE(resctrl_dir); + VIR_FREE(schemata_str); + VIR_FREE(schemata_file); + VIR_FREE(domain_file); + VIR_FREE(vcpus); + virDomainDefFree(def); + return ret; +} + +static int +mymain(void) +{ + struct virResctrlData data = {0}; + int ret = 0; + +#define DO_TEST_FREE(_filename) \ + do { \ + data = (struct virResctrlData) { .filename = _filename }; \ + if (virTestRun("Free: " _filename, test_virResctrlGetFree, &data) < 0) \ + ret = -1; \ + } while (0) + +#define DO_TEST_ALLOC_FULL(_filename, _test_name, _fail) \ + do { \ + data = (struct virResctrlData) { .filename = _filename, \ + .test_name = _test_name, \ + .fail = _fail }; \ + if (virTestRun("Alloc: " _filename "+" _test_name, \ + test_virResctrlGetAlloc, &data) < 0) \ + ret = -1; \ + } while (0) + +#define DO_TEST_ALLOC(_filename, _test_name) \ + DO_TEST_ALLOC_FULL(_filename, _test_name, false) + +#define DO_TEST_ALLOC_FAIL(_filename, _test_name) \ + DO_TEST_ALLOC_FULL(_filename, _test_name, true) + + DO_TEST_FREE("resctrl"); + DO_TEST_FREE("resctrl-cdp"); + DO_TEST_FREE("resctrl-skx"); + DO_TEST_FREE("resctrl-skx-twocaches"); + + + /************* + * Tests with generic-cachetune.xml (decently sized allocations for 2 caches) + ***/ + + /* Simplest one, this one should fail because there is not resctrl support + * on the host */ + DO_TEST_ALLOC_FAIL("caches", "cachetune"); + + DO_TEST_ALLOC("resctrl", "cachetune"); + + /* This one should fail because there are allocations for two caches + * requested, but only one exists */ + DO_TEST_ALLOC_FAIL("resctrl-skx", "cachetune"); + + /* This one should fail because there is not enough room available */ + DO_TEST_ALLOC_FAIL("resctrl-skx-twocaches", "cachetune"); + + /* This one should fail because non-CDP allocation is requested on + * CDP-enabled system, support for this can be added in the future */ + DO_TEST_ALLOC_FAIL("resctrl-cdp", "cachetune"); + + /************* + * Tests with generic-cachetune-small.xml (small allocation for 1 cache) + ***/ + + /* This one should fail because the allocation size is smaller than minimum + * (although with the right granularity) */ + DO_TEST_ALLOC_FAIL("resctrl", "cachetune-small"); + + /* These two should fail because the allocations size is smaller than + * granularity */ + DO_TEST_ALLOC_FAIL("resctrl-skx", "cachetune-small"); + DO_TEST_ALLOC_FAIL("resctrl-skx-twocaches", "cachetune-small"); + + /* This one should fail because non-CDP allocation is requested on + * CDP-enabled system, support for this can be added in the future */ + DO_TEST_ALLOC_FAIL("resctrl-cdp", "cachetune-small"); + + /************* + * Tests with generic-cachetune-cdp.xml (decently sized allocations, + * separate for code and data) + ***/ + + /* These should fail because CDP allocation is requested on non-CDP + * system */ + DO_TEST_ALLOC_FAIL("resctrl", "cachetune-cdp"); + DO_TEST_ALLOC_FAIL("resctrl-skx", "cachetune-cdp"); + DO_TEST_ALLOC_FAIL("resctrl-skx-twocaches", "cachetune-cdp"); + + DO_TEST_ALLOC("resctrl-cdp", "cachetune-cdp"); + + return ret; +} + +VIR_TEST_MAIN(mymain) -- 2.15.0

On 11/13/2017 03:50 AM, Martin Kletzander wrote:
This test has 2 different uses:
1) DO_TEST_FREE initializes capabilities from vircaps2xmldata (since it exists there already) and then requests list of free bitmaps (all unallocated space) from virresctrl.c
2) DO_TEST_ALLOC takes capabilities from vircaps2xmldata, and uses resctrl info to request an allocation from virresctrl.c for a VM from genericxml2xmlindata.
Desirable outputs are saved in virresctrldata.
Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- tests/Makefile.am | 8 +- .../resctrl--cachetune/vcpus-0-1.alloc | 1 + .../resctrl--cachetune/vcpus-3.alloc | 1 + .../resctrl-cdp--cachetune-cdp/vcpus-0-1.alloc | 2 + .../resctrl-cdp--cachetune-cdp/vcpus-2.alloc | 1 + .../resctrl-cdp--cachetune-cdp/vcpus-3.alloc | 1 + tests/virresctrldata/resctrl-cdp.schemata | 2 + .../virresctrldata/resctrl-skx-twocaches.schemata | 1 + tests/virresctrldata/resctrl-skx.schemata | 1 + tests/virresctrldata/resctrl.schemata | 1 + tests/virresctrltest.c | 277 +++++++++++++++++++++ 11 files changed, 295 insertions(+), 1 deletion(-) create mode 100644 tests/virresctrldata/resctrl--cachetune/vcpus-0-1.alloc create mode 100644 tests/virresctrldata/resctrl--cachetune/vcpus-3.alloc create mode 100644 tests/virresctrldata/resctrl-cdp--cachetune-cdp/vcpus-0-1.alloc create mode 100644 tests/virresctrldata/resctrl-cdp--cachetune-cdp/vcpus-2.alloc create mode 100644 tests/virresctrldata/resctrl-cdp--cachetune-cdp/vcpus-3.alloc create mode 100644 tests/virresctrldata/resctrl-cdp.schemata create mode 100644 tests/virresctrldata/resctrl-skx-twocaches.schemata create mode 100644 tests/virresctrldata/resctrl-skx.schemata create mode 100644 tests/virresctrldata/resctrl.schemata create mode 100644 tests/virresctrltest.c
I'm not sure any mor amount of looking at this will help me figure out how this all works. I mean I understand how the test works, but how the CAT works - I'm lost. I honestly didn't follow the previous conversation on all this and trying to figure out how those schemata impact the XML or vice versa - thoroughly and completely lost. I can understand the need/desire for the technology but wonder if someone over engineered the simple rope swing the customer was looking to create (not the libvirt patches per se). There's so much detail in each cache line that one really needs to "know" a lot more than I (currently) have the desire to read up on. I can only imagine (as you noted a few patches earlier) how much thinking about CAT(s ;-)) has dominated your life lately.
From the aspect of it looks like a test, it works, then yeap - good to go. I'll be intrigued to see how qe tests/breaks this ;-)!
Reviewed-by: John Ferlan <jferlan@redhat.com>, but not really fully understaood, John

On Wed, Nov 15, 2017 at 05:00:34PM -0500, John Ferlan wrote:
On 11/13/2017 03:50 AM, Martin Kletzander wrote:
This test has 2 different uses:
1) DO_TEST_FREE initializes capabilities from vircaps2xmldata (since it exists there already) and then requests list of free bitmaps (all unallocated space) from virresctrl.c
2) DO_TEST_ALLOC takes capabilities from vircaps2xmldata, and uses resctrl info to request an allocation from virresctrl.c for a VM from genericxml2xmlindata.
Desirable outputs are saved in virresctrldata.
Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- tests/Makefile.am | 8 +- .../resctrl--cachetune/vcpus-0-1.alloc | 1 + .../resctrl--cachetune/vcpus-3.alloc | 1 + .../resctrl-cdp--cachetune-cdp/vcpus-0-1.alloc | 2 + .../resctrl-cdp--cachetune-cdp/vcpus-2.alloc | 1 + .../resctrl-cdp--cachetune-cdp/vcpus-3.alloc | 1 + tests/virresctrldata/resctrl-cdp.schemata | 2 + .../virresctrldata/resctrl-skx-twocaches.schemata | 1 + tests/virresctrldata/resctrl-skx.schemata | 1 + tests/virresctrldata/resctrl.schemata | 1 + tests/virresctrltest.c | 277 +++++++++++++++++++++ 11 files changed, 295 insertions(+), 1 deletion(-) create mode 100644 tests/virresctrldata/resctrl--cachetune/vcpus-0-1.alloc create mode 100644 tests/virresctrldata/resctrl--cachetune/vcpus-3.alloc create mode 100644 tests/virresctrldata/resctrl-cdp--cachetune-cdp/vcpus-0-1.alloc create mode 100644 tests/virresctrldata/resctrl-cdp--cachetune-cdp/vcpus-2.alloc create mode 100644 tests/virresctrldata/resctrl-cdp--cachetune-cdp/vcpus-3.alloc create mode 100644 tests/virresctrldata/resctrl-cdp.schemata create mode 100644 tests/virresctrldata/resctrl-skx-twocaches.schemata create mode 100644 tests/virresctrldata/resctrl-skx.schemata create mode 100644 tests/virresctrldata/resctrl.schemata create mode 100644 tests/virresctrltest.c
I'm not sure any mor amount of looking at this will help me figure out how this all works. I mean I understand how the test works, but how the CAT works - I'm lost. I honestly didn't follow the previous conversation on all this and trying to figure out how those schemata impact the XML or vice versa - thoroughly and completely lost. I can understand the need/desire for the technology but wonder if someone over engineered the simple rope swing the customer was looking to create (not the libvirt patches per se). There's so much detail in each cache line that one really needs to "know" a lot more than I (currently) have the desire to read up on. I can only imagine (as you noted a few patches earlier) how much thinking about CAT(s ;-)) has dominated your life lately.
From the aspect of it looks like a test, it works, then yeap - good to go. I'll be intrigued to see how qe tests/breaks this ;-)!
Reviewed-by: John Ferlan <jferlan@redhat.com>, but not really fully understaood,
I've checked the data and it looks correct so Reviewed-by: Pavel Hrdina <phrdina@redhat.com>

Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- src/qemu/qemu_process.c | 61 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 7df440ee4345..335146ab71b8 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -2478,6 +2478,33 @@ qemuProcessSetupEmulator(virDomainObjPtr vm) } +static int +qemuProcessResctrlCreate(virQEMUDriverPtr driver, + virDomainObjPtr vm) +{ + int ret = -1; + size_t i = 0; + virCapsPtr caps = virQEMUDriverGetCapabilities(driver, false); + qemuDomainObjPrivatePtr priv = vm->privateData; + + if (!caps) + return -1; + + for (i = 0; i < vm->def->ncachetunes; i++) { + if (virResctrlAllocCreate(caps->host.resctrl, + vm->def->cachetunes[i]->alloc, + "qemu", + priv->machineName) < 0) + goto cleanup; + } + + ret = 0; + cleanup: + virObjectUnref(caps); + return ret; +} + + static int qemuProcessInitPasswords(virConnectPtr conn, virQEMUDriverPtr driver, @@ -4899,12 +4926,26 @@ qemuProcessSetupVcpu(virDomainObjPtr vm, { pid_t vcpupid = qemuDomainGetVcpuPid(vm, vcpuid); virDomainVcpuDefPtr vcpu = virDomainDefGetVcpu(vm->def, vcpuid); + size_t i = 0; - return qemuProcessSetupPid(vm, vcpupid, VIR_CGROUP_THREAD_VCPU, - vcpuid, vcpu->cpumask, - vm->def->cputune.period, - vm->def->cputune.quota, - &vcpu->sched); + if (qemuProcessSetupPid(vm, vcpupid, VIR_CGROUP_THREAD_VCPU, + vcpuid, vcpu->cpumask, + vm->def->cputune.period, + vm->def->cputune.quota, + &vcpu->sched) < 0) + return -1; + + for (i = 0; i < vm->def->ncachetunes; i++) { + virDomainCachetuneDefPtr ct = vm->def->cachetunes[i]; + + if (virBitmapIsBitSet(ct->vcpus, vcpuid)) { + if (virResctrlAllocAddPID(ct->alloc, vcpupid) < 0) + return -1; + break; + } + } + + return 0; } @@ -5786,6 +5827,10 @@ qemuProcessLaunch(virConnectPtr conn, if (qemuProcessSetupEmulator(vm) < 0) goto cleanup; + VIR_DEBUG("Setting up resctrlfs"); + if (qemuProcessResctrlCreate(driver, vm) < 0) + goto cleanup; + VIR_DEBUG("Setting domain security labels"); if (qemuSecuritySetAllLabel(driver, vm, @@ -6434,6 +6479,12 @@ void qemuProcessStop(virQEMUDriverPtr driver, vm->def->name); } + /* Remove resctrl allocation after cgroups are cleaned up which makes it + * kind of safer (although removing the allocation should work even with + * pids in tasks file */ + for (i = 0; i < vm->def->ncachetunes; i++) + virResctrlAllocRemove(vm->def->cachetunes[i]->alloc); + qemuProcessRemoveDomainStatus(driver, vm); /* Remove VNC and Spice ports from port reservation bitmap, but only if -- 2.15.0

Commit messages have either gone on a diet or the writer is on strike On 11/13/2017 03:50 AM, Martin Kletzander wrote:
Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- src/qemu/qemu_process.c | 61 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 5 deletions(-)
This seems reasonable too, Reviewed-by: John Ferlan <jferlan@redhat.com> John
diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 7df440ee4345..335146ab71b8 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c
[...]
@@ -6434,6 +6479,12 @@ void qemuProcessStop(virQEMUDriverPtr driver, vm->def->name); }
+ /* Remove resctrl allocation after cgroups are cleaned up which makes it + * kind of safer (although removing the allocation should work even with + * pids in tasks file */ + for (i = 0; i < vm->def->ncachetunes; i++) + virResctrlAllocRemove(vm->def->cachetunes[i]->alloc); +
Oh and here is Stop doing the Remove... Perhaps why earlier since I hadn't read something like this so perhaps why I asked about it before checking consumers of AllocRemove
qemuProcessRemoveDomainStatus(driver, vm);
/* Remove VNC and Spice ports from port reservation bitmap, but only if

Signed-off-by: Martin Kletzander <mkletzan@redhat.com> --- docs/news.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/news.xml b/docs/news.xml index ef855d895886..e06c981e36da 100644 --- a/docs/news.xml +++ b/docs/news.xml @@ -35,6 +35,17 @@ <libvirt> <release version="v3.10.0" date="unreleased"> <section title="New features"> + <change> + <summary> + Added support for CAT (resctrl tuning) + </summary> + <description> + Domain vCPU threads can now have allocated some parts of host cache + using <code>cachetune</code> element in <code>cputune</code>. The + support is limited in some ways, but in the future it could be + extended. + </description> + </change> </section> <section title="Improvements"> </section> -- 2.15.0

On 11/13/2017 03:50 AM, Martin Kletzander wrote: > Signed-off-by: Martin Kletzander <mkletzan@redhat.com> > --- > docs/news.xml | 11 +++++++++++ > 1 file changed, 11 insertions(+) > > diff --git a/docs/news.xml b/docs/news.xml > index ef855d895886..e06c981e36da 100644 > --- a/docs/news.xml > +++ b/docs/news.xml > @@ -35,6 +35,17 @@ > <libvirt> > <release version="v3.10.0" date="unreleased"> > <section title="New features"> > + <change> > + <summary> > + Added support for CAT (resctrl tuning) Actually Cache Allocation Technology, right? > + </summary> > + <description> > + Domain vCPU threads can now have allocated some parts of host cache > + using <code>cachetune</code> element in <code>cputune</code>. The using the > + support is limited in some ways, but in the future it could be > + extended. Instead of "in some ways, but..." - let's just describe the basics that got implemented. The ability to define for vCPU(s) how CAT will be configured for the domain for the cache id, level, and size. I trust you can come up with something more complete without writing too much. Reviewed-by: John Ferlan <jferlan@redhat.com> John > + </description> > + </change> > </section> > <section title="Improvements"> > </section> >

On Wed, Nov 15, 2017 at 05:22:24PM -0500, John Ferlan wrote: > > >On 11/13/2017 03:50 AM, Martin Kletzander wrote: >> Signed-off-by: Martin Kletzander <mkletzan@redhat.com> >> --- >> docs/news.xml | 11 +++++++++++ >> 1 file changed, 11 insertions(+) >> > diff --git a/docs/news.xml b/docs/news.xml >> index ef855d895886..e06c981e36da 100644 >> --- a/docs/news.xml >> +++ b/docs/news.xml >> @@ -35,6 +35,17 @@ >> <libvirt> >> <release version="v3.10.0" date="unreleased"> >> <section title="New features"> >> + <change> >> + <summary> >> + Added support for CAT (resctrl tuning) > >Actually Cache Allocation Technology, right? > I don't know why I wanted to push resctrl in somehow. Fixed. >> + </summary> >> + <description> >> + Domain vCPU threads can now have allocated some parts of host cache >> + using <code>cachetune</code> element in <code>cputune</code>. The > >using the > >> + support is limited in some ways, but in the future it could be >> + extended. > >Instead of "in some ways, but..." - let's just describe the basics that >got implemented. The ability to define for vCPU(s) how CAT will be >configured for the domain for the cache id, level, and size. > I left that part out and documented what's supported in, well, the documentation. There is no need to have this part in news.xml, I guess. Anyway, there'll be everything in v2 later. >I trust you can come up with something more complete without writing too >much. > >Reviewed-by: John Ferlan <jferlan@redhat.com> > > >John >> + </description> >> + </change> >> </section> >> <section title="Improvements"> >> </section> >>
participants (3)
-
John Ferlan
-
Martin Kletzander
-
Pavel Hrdina