[libvirt] [PATCHv5 01/19] docs: Refactor schemas to support default allocation

John Ferlan jferlan at redhat.com
Wed Oct 10 21:28:56 UTC 2018



On 10/10/18 9:44 AM, Wang, Huaqiang wrote:
> 
> 
>> -----Original Message-----
>> From: John Ferlan [mailto:jferlan at redhat.com]
>> Sent: Wednesday, October 10, 2018 4:36 AM
>> To: Wang, Huaqiang <huaqiang.wang at intel.com>; libvir-list at redhat.com
>> Cc: Feng, Shaohe <shaohe.feng at intel.com>; Niu, Bing <bing.niu at intel.com>;
>> Ding, Jian-feng <jian-feng.ding at intel.com>; Zang, Rui <rui.zang at intel.com>
>> Subject: Re: [libvirt] [PATCHv5 01/19] docs: Refactor schemas to support default
>> allocation
>>
>>
>> s/docs/conf,util/
>>
>> It's more than just docs...
> 
> Yes, the title will be changed accordingly.
> 
>>
>> On 10/9/18 6:30 AM, Wang Huaqiang wrote:
>>> The resctrl default allocation is introduced in this patch, which
>>> refers to the root directory (/sys/fs/resctrl) and immediately be
>>> created after mounting, owns all the tasks and cpus in the system and
>>> can make full use of all resources.
>>>
>>> It does not intentionally allocate any dedicated amount of resource,
>>> either cache or memory bandwidth, for default allocation.
>>>
>>> If a system task has no resource control applied but you want to know
>>> task's cache or memroy bandwidth utilization information, the default
>>> allocation is meaningful. We create resctrl monitor under the default
>>> allocation for such kind of task.
>>>
>>> Refactoring schemas docs and APIs to create a default cache allocation
>>> by allowing the appearance of an <cachetune> with no <cache> element.
>>>
>>> Signed-off-by: Wang Huaqiang <huaqiang.wang at intel.com>
>>> ---
>>>  docs/formatdomain.html.in     |  4 ++--
>>>  docs/schemas/domaincommon.rng |  4 ++--
>>>  src/conf/domain_conf.c        | 32 +++++++++++++++++++-------------
>>>  src/util/virresctrl.c         | 27 +++++++++++++++++++++++++++
>>>  4 files changed, 50 insertions(+), 17 deletions(-)
>>
>> How would this look in XML output - supply a <cachetune> w/o the <cache>
>> element? Probably also need the <monitor> there as well in at least one entry
>> just prove it works.
> 
> If no <monitor> and no <cache> parsed from XML, the <cachetune> element will
> not be shown. The <cachetune> element only could be seen if there is at least
> one <cache> or <monitor> element.
> 

In a way this has become obvious as I've gone through things, but after
really thinking through 13 patches, I'm not sure it matters if a
<cachetune> entry exists without <cache> or <monitor>. It does nothing
and can be documented that way.  Far too much work and effort goes into
concerning ourselves with concepts that in the end don't seem to matter
and perhaps would never be done.  But if they are done (e.g. <cachetune>
without <cache> and <monitor>), so what it does nothing and can be
documented that way.

> Following layouts could not be seen in XML:
> 
>     <cputune>
>     ...
>       <cachetune  vcpus='0'/>
>     </cputune>
> 
> 'Resctrl monitor' has not yet been introduced until this patch, for a
> better understanding of purpose of this patch, let me take an example
> to illustrate what will happen after applying whole series patches.
> 
> Supposing we are going to create a monitor over vcpu 0 for getting cache
> utilization, and we haven't created any cache allocation for vcpu 0.
> This could happen if user want to know the cache usage of specific vcpu but
> don't want to allocate any dedicated amount of cache for it.
> 
> The XML layout would be:
> 
>     <cputune>
>       <cachetune  vcpus='0'>
>         <monitor level='3' vcpus='0'/>
>       </cachetune>
>     </cputune>
> 
> To support above XML layout in future patches, we need to append a
> virDomainResctrlDef element to @(virDomainDefPtr*)def->resctrls
> even the virDomainResctrlDef.alloc is empty. This patch changes
> code implement this and also will not create any allocation for
> cache if @def->resctrls[i]->alloc is set to NULL.

If someone has a <cachetune> element, then they get an resctrl->alloc.
If it doesn't have <cache> elements (all that matters at this point),
who cares.

> 
> Also this monitor specified in above configuration will be created under
> '/sys/fs/resctrl/mon_groups'.
> 
> This piece of code has been refactored for several times in this patch and
> subsequent patches, each patch works and does not break original
> functionality already implemented. But the functionality of resctrl
> monitor only works after whole series have been applied.
> 
>>
>> It seems <memorytune> entries have their own unique "back door" of sorts
>> calling virResctrlAllocNew when there are no <cachetune> entries.
> 
> Yes. memorytune creates separate entry in <cputune>.
> 
>> Similar to what happens if there were entries cachetune for vcpus of "0-1" and
>> "2-5", but nothing for "6-7". The assumption always being <memorytune> read
>> after <cachetune> and as long as there's no overlap, just create the
>> <memorytune> entry, by supplying a <cachetune> entry without <cache> entries.
>>
> 
> Not understand above paragraph too much.
> Supposing your 'cachetune' entry means an corresponding element in
> @def->resctrls array.

This probably crossed boundaries, but the point was if <memorytune>
didn't find a <cachetune> for the 'vcpus' it had, then it creates one.
But this patch goes through a few handstands to not create ->alloc when
as I've come to realize later it really doesn't seem to need to do.

> 
> Up to this patch, it is not allowed to append the virDomainResctrlDef element
> to @def->resctrls if there is no <cache> element.
> 
> Later, a virDomainResctrlDef element could only be appended if there exists
> at least one <cache> element or one <monitor>.
> 
>> supplying a <cachetune> entry without <cache> entries.
> 
> A <cachetune> entry without <cache>entries, and at same time, without
> <monitor> entries is not permitted here.
> 
>> It's a little awkward to read, but now makes me think about all the
>> existing/strange linkages. In a way I suppose having no <cachetune> entries is
>> proven to work by tests/genericxml2xmlindata/memorytune.xml.
>> The reality is they get created, but without visibility.
> 
> What is created and no visibility? Not understand.

It's that memorytune path I noted above. Nothing is ever saved with
them, but they do exist 'internally' (virDomainMemorytuneDefParse calls
virResctrlAllocNew and will eventually virDomainResctrlAppend because
virResctrlAllocIsEmpty is false since memorytune would have a @bandwidth
(and virResctrlAllocSetMemoryBandwidth fills in alloc->mem_bw).

> If no <cachtune> entries, there will no virDomainResctrlDef element appended
> to @def->resctrls.
> 
> Up to this patch, there will be no creation for either <cachtune> entry in
> later invocation of virDomainCachetuneDefFormat or an appending of element in
> @def->resctrls during XML parsing, even with following XML configuration:
> 
>     <cputune>
>     ...
>       <cachetune  vcpus='0-1'/>
>     </cputune>
> 
> Even after an integration of the whole patch series, upper XML configuration
> will not create any @def->resctrls elements in configuration file parsing or
> any <cachetune> XML element in later call of virDomainCachetuneDefFormat.
> 
>>
>>>
>>> diff --git a/docs/formatdomain.html.in b/docs/formatdomain.html.in
>>> index 8189959..b1651e3 100644
>>> --- a/docs/formatdomain.html.in
>>> +++ b/docs/formatdomain.html.in
>>> @@ -943,8 +943,8 @@
>>>          <dl>
>>>            <dt><code>cache</code></dt>
>>>            <dd>
>>> -            This element controls the allocation of CPU cache and has the
>>> -            following attributes:
>>> +            This optional element controls the allocation of CPU cache and has
>>> +            the following attributes:
>>>              <dl>
>>>                <dt><code>level</code></dt>
>>>                <dd>
>>> diff --git a/docs/schemas/domaincommon.rng
>>> b/docs/schemas/domaincommon.rng index 099a949..5c533d6 100644
>>> --- a/docs/schemas/domaincommon.rng
>>> +++ b/docs/schemas/domaincommon.rng
>>> @@ -956,7 +956,7 @@
>>>              <attribute name="vcpus">
>>>                <ref name='cpuset'/>
>>>              </attribute>
>>> -            <oneOrMore>
>>> +            <zeroOrMore>
>>>                <element name="cache">
>>>                  <attribute name="id">
>>>                    <ref name='unsignedInt'/> @@ -980,7 +980,7 @@
>>>                    </attribute>
>>>                  </optional>
>>>                </element>
>>> -            </oneOrMore>
>>> +            </zeroOrMore>
>>>            </element>
>>>          </zeroOrMore>
>>>          <zeroOrMore>
>>> diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index
>>> 9911d56..b77680e 100644
>>> --- a/src/conf/domain_conf.c
>>> +++ b/src/conf/domain_conf.c
>>> @@ -19002,22 +19002,27 @@
>> virDomainCachetuneDefParse(virDomainDefPtr def,
>>>          goto cleanup;
>>>      }
>>>
>>> -    if (virDomainResctrlVcpuMatch(def, vcpus, &alloc) < 0)
>>> -        goto cleanup;
>>> -
>>> -    if (!alloc) {
>>> -        alloc = virResctrlAllocNew();
>>> -        if (!alloc)
>>> +    /* If 'n' equals 0, then no <cache> element found in <cachetune>,
>>> +     * this means it is a default alloction. For default allocation,
>>
>> s/alloction/allocation
> 
> My mistake. will be fixed.
> 
>>
>>> +     * @SetvirDomainResctrlDefPtr->alloc is set to NULL */
>>
>> Not sure what ^^ this was...
> 
> Should be @virDomainResctrlDefPtr->alloc.
> 
>>
>>> +    if (n != 0) {
>>> +        if (virDomainResctrlVcpuMatch(def, vcpus, &alloc) < 0)
>>>              goto cleanup;
>>> -    } else {
>>> -        virReportError(VIR_ERR_XML_ERROR, "%s",
>>> -                       _("Identical vcpus in cachetunes found"));
>>> -        goto cleanup;
>>> -    }
>>
>> diff is perhaps easier to read if you:
>>
>>     if (n == 0) {
>>         ret = 0;
>>         goto cleanup;
>>     }
>>
>> then none of the indent is needed for n != 0
> 
> Your advising changes works here, but will conflict with later logic I will
> introduce in patch 13.

Yeah about where I stopped and started thinking when a <cachetune> is
see we create the @alloc

> 
> This part of code will be modified in later patch (patch 13 of 19),
> adding some code parsing configuration for monitor. At that time,
> if n==0 then letting this function return without error is not
> a reasonable logic, and need to check if <monitor> entries exists
> under <cachetune> entry.
> 
> Paste the code here for your reference:
> 
> 19180     if ((n = virXPathNodeSet("./cache", ctxt, &nodes)) < 0) {
> 19181         virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> 19182                        _("Cannot extract cache nodes under cachetune"));
> 19183         goto cleanup;
> 19184     }
> 19185 
> 19186     /* If 'n' equals 0, then no <cache> element found in <cachetune>,
> 19187      * this means it is a default alloction. For default allocation,
> 19188      * @virDomainResctrlDefPtr->alloc is set to NULL */
> 19189     if (n != 0) {

But from here...

> 19190         if (virDomainResctrlVcpuMatch(def, vcpus, &alloc) < 0)
> 19191             goto cleanup;
> 19192 
> 19193         if (!alloc) {
> 19194             alloc = virResctrlAllocNew();
> 19195             if (!alloc)
> 19196                 goto cleanup;
> 19197         } else {
> 19198             virReportError(VIR_ERR_XML_ERROR, "%s",
> 19199                            _("Identical vcpus in cachetunes found"));
> 19200             goto cleanup;
> 19201         }
> 19202 

...to here has nothing to do with whether <cache> elements exist, so why
would we restrict creation of @alloc based on whether <cache> entries
existed.  So what if no <cache> entries exist.

This is perhaps less "default allocation" and more don't require <cache>
elements. In that case, ... <whatever it means>...  Later we're going to
add <monitor> elements and they don't require <cache> elements, so
having ->alloc predicated on whether <cache> entries exists complicates
a lot of code. Simplify things.

> 19203         for (i = 0; i < n; i++) {
> 19204             if (virDomainCachetuneDefParseCache(ctxt, nodes[i], alloc) < 0)
> 19205                 goto cleanup;
> 19206         }
> 19207     }
> 19208 
> 19209     resctrl = virDomainResctrlNew(alloc, vcpus);
> 19210     if (!resctrl)
> 19211         goto cleanup;
> 19212 
> 19213     if (virDomainResctrlMonDefParse(def, ctxt, node,
> 19214                                     VIR_RESCTRL_MONITOR_TYPE_CACHE,
> 19215                                     resctrl) < 0)
> 19216         goto cleanup;
> 19217 
> 19218     if (virResctrlAllocIsEmpty(alloc) && !resctrl->nmonitors) {
> 
>     --> If there is no 'cache' entry and no 'monitor' entry in current
>         <cachetune> entry, code will go to this place, and function will
>         return normally without error, and  virDomainResctrlAppend will
>         not be executed.
> 

But a <memorytune> can allocate and insert too.

As I pointed out later I think the ResctrlNew and ResctrlAppend don't
need to be separate either.

> 19219         ret = 0;
> 19220         goto cleanup;
> 19221     }
> 19222 
> 19223     if (virDomainResctrlAppend(def, node, resctrl, flags) < 0)
> 19224         goto cleanup;
> 19225 
> 19226     resctrl = NULL;
> 19227     ret = 0;
> 19228  cleanup:
> 19229     ctxt->node = oldnode;
> 19230     virDomainResctrlDefFree(resctrl);
> 19231     virObjectUnref(alloc);
> 19232     virBitmapFree(vcpus);
> 19233     VIR_FREE(nodes);
> 19234     return ret;
> 
>>
>>>
>>> -    for (i = 0; i < n; i++) {
>>> -        if (virDomainCachetuneDefParseCache(ctxt, nodes[i], alloc) < 0)
>>> +        if (!alloc) {
>>> +            alloc = virResctrlAllocNew();
>>> +            if (!alloc)
>>> +                goto cleanup;
>>> +        } else {
>>> +            virReportError(VIR_ERR_XML_ERROR, "%s",
>>> +                           _("Identical vcpus in cachetunes found"));
>>>              goto cleanup;
>>> +        }
>>> +
>>> +        for (i = 0; i < n; i++) {
>>> +            if (virDomainCachetuneDefParseCache(ctxt, nodes[i], alloc) < 0)
>>> +                goto cleanup;
>>> +        }
>>>      }
>>>
>>>      if (virResctrlAllocIsEmpty(alloc)) { @@ -19027,6 +19032,7 @@
>>> virDomainCachetuneDefParse(virDomainDefPtr def,
>>>
>>>      if (virDomainResctrlAppend(def, node, alloc, vcpus, flags) < 0)
>>>          goto cleanup;
>>> +
>>
>> Superfluous
> 
> This blank line is involved by mistake, will be removed :)
> 
>>
>>>      vcpus = NULL;
>>>      alloc = NULL;
>>>
>>> diff --git a/src/util/virresctrl.c b/src/util/virresctrl.c index
>>> df5b512..697424c 100644
>>> --- a/src/util/virresctrl.c
>>> +++ b/src/util/virresctrl.c
>>> @@ -234,6 +234,10 @@ virResctrlInfoMonFree(virResctrlInfoMonPtr mon)
>>>   * in case there is no allocation for that particular cache allocation (level,
>>>   * cache, ...) or memory allocation for particular node).
>>>   *
>>> + * Resctrl file system root directory, /sys/fs/sysctrl/, is called
>>> + the default
>>> + * allocation, which is created, immediately after mounting, owns all
>>> + the
>>> + * tasks and cpus in the system and can make full use of all resources.
>>> + *
>>>   * =====Cache allocation technology (CAT)=====
>>>   *
>>>   * Since one allocation can be made for caches on different levels,
>>> the first
>>
>> I assume you searched on:
>>
>>     virResctrlAllocGetType  (w/ callers:)
>>       virResctrlAllocUpdateMask
>>       virResctrlAllocUpdateSize
>>       virResctrlAllocCopyMasks
>>
>> It's kind of "painful" to back trace all the callers and determine if any/each of
>> them does the if (!alloc) check "originally" somewhere. I took a quick look and
>> they seem OK
> 
> Yes. and I also double checked for these functions.
> 
> I am focus on cache monitor entries in these patches, will be further
> checked when introducing memoryBW monitor later.
> 
>>
>>> @@ -1165,6 +1169,9 @@ virResctrlAllocSetCacheSize(virResctrlAllocPtr alloc,
>>>                              unsigned int cache,
>>>                              unsigned long long size)  {
>>> +    if (!alloc)
>>> +        return 0;
>>> +
>>>      if (virResctrlAllocCheckCollision(alloc, level, type, cache)) {
>>>          virReportError(VIR_ERR_XML_ERROR,
>>>                         _("Colliding cache allocations for cache "
>>> @@ -1235,6 +1242,9 @@
>>> virResctrlAllocSetMemoryBandwidth(virResctrlAllocPtr alloc,  {
>>>      virResctrlAllocMemBWPtr mem_bw = alloc->mem_bw;
>>>
>>
>> ^^ This wouldn't have been too happy would it if alloc == NULL; however,
>>
>>
>>> +    if (!alloc)
>>> +        return 0;
>>> +
>>
>> I don't think it'll matter since the only caller is virDomainMemorytuneDefParse
>> which will allocate an @alloc if one didn't exist *and* pass that through to here,
>> so this check shouldn't be necessary.
> 
> I don't realize the @alloc has already been used!
> Will remove the pointer checking for @alloc.
> 

And yes, this is where/why I think you shouldn't have a concept of
default allocation... It should just be an allocation (aka cachetune)
without specific cache entries.  Later that allows monitor entries, but
it may allow something else now, who knows.

Again, as I pointed out you can have a domain with <memorytune> only
entries and have the same "thing", so there's absolutely no reason to
not just allow <cachetune> without <cache>.


>>
>> In researching this I realized that although we have a memorytune-colliding-
>> allocs.xml and memorytune.xml, there is no <memorytune> example that
>> includes <cachetune> entries as well.
> 
> Let me add a test for this case in next update.
> 
>>
>>>      if (memory_bandwidth > 100) {
>>>          virReportError(VIR_ERR_XML_ERROR, "%s",
>>>                         _("Memory Bandwidth value exceeding 100 is
>>> invalid.")); @@ -1304,6 +1314,11 @@ int
>>> virResctrlAllocSetID(virResctrlAllocPtr alloc,
>>>                       const char *id)
>>>  {
>>> +    /* If passed a default allocation in, @alloc will be NULL. This is
>>> +     * a valid case, return normally. */
> 
> Will remove the comment.
> I'll try to add this comment to the CAT and MBA comments.
> 
>>
>> This is the only one to get that type of comment... Probably something that
>> should instead be more clearly indicated perhaps in the CAT and MBA comments
>> at the top of the module.
>>
>>> +    if (!alloc)
>>> +        return 0;
>>> +
>>>      if (!id) {
>>>          virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
>>>                         _("Resctrl allocation 'id' cannot be NULL"));
>>> @@ -1317,6 +1332,9 @@ virResctrlAllocSetID(virResctrlAllocPtr alloc,
>>> const char *  virResctrlAllocGetID(virResctrlAllocPtr alloc)  {
>>> +    if (!alloc)
>>> +        return NULL;
>>> +
>>
>> Probably need to consider current callers... I see that both
>> virDomainCachetuneDefFormat and virDomainMemorytuneDefFormat would
>> return -1 for some unknown reason.  Although perhaps the latter would work
>> fine since it'd create it's own @alloc if resctrl->alloc == NULL.
>>
>> Hence why I asked for an XML example above.
> 
> There is a subsequent patch, patch 16, handling this.

I never got there, heap overflowed stack.

I'm currently of the opinion that all of the "if (!alloc)" checks won't
be necessary if you create the resctrl->alloc once a <cachetune> entry
is seen. Similarly, if a <memorytune> references 'vcpus' that don't
already have a <cachetune> entry, then one is created - whether it's
formatted is a different story (currently it's not, which is fine). I
think that'll simplify things.

John

> 
> Up to now, no monitor introduced, there will not there is no way to
> pass in an empty @alloc, so the code will not introduce any trouble.
> 
> In patch 16, a 'virDomainResctrl.id' is introduced, and later code will
> use 'virDomainResctrlDef.id' to track the id of cachetune. I did this, because
> I have extended the scope of virDomainResctrlDef in later patches by adding
> monitors, and one virDomainResctrlDef is the abstraction of one <cachetune>
> entry, so logically 'id' of <cachetune> should be kept in structure
> virDomainResctrlDef.
> 
>>
>>>      return alloc->id;
>>>  }
>>>
>>> @@ -2209,6 +2227,9 @@ int
>>>  virResctrlAllocDeterminePath(virResctrlAllocPtr alloc,
>>>                               const char *machinename)  {
>>> +    if (!alloc)
>>> +        return 0;
>>> +
>>>      if (!alloc->id) {
>>>          virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
>>>                         _("Resctrl Allocation ID must be set before
>>> creation")); @@ -2302,6 +2323,9 @@ virResctrlAllocAddPID(virResctrlAllocPtr
>> alloc,
>>>      char *pidstr = NULL;
>>>      int ret = 0;
>>>
>>> +    if (!alloc)
>>> +        return 0;
>>> +
>>>      if (!alloc->path) {
>>>          virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
>>>                         _("Cannot add pid to non-existing resctrl
>>> allocation")); @@ -2334,6 +2358,9 @@
>>> virResctrlAllocRemove(virResctrlAllocPtr alloc)  {
>>>      int ret = 0;
>>>
>>> +    if (!alloc)
>>> +        return 0;
>>> +
>>>      if (!alloc->path)
>>>          return 0;
>>
>> These two could be combined
> 
> Will be combined.
> 
>>
>> John
> 
> 
> Thanks for review.
> Huaqiang
>>>
>>>
> 




More information about the libvir-list mailing list