Repository: conan-io/conan

Issue:
[bug] get_cpus is not supporting cgroup2
The current detection of https://github.com/conan-io/conan/blob/1.46.2/conans/client/tools/oss.py#L36-L39 seems no longer applicable in case of cgroup2 as the `/sys/fs/cgroup/cpu/cpu.cfs_quota_us` and `/sys/fs/cgroup/cpu/cpu.cfs_period_us` are replaced by `/sys/fs/cgroup/cpu.max` (in case of docker executed with `--cpus`).

There could be checked if cgroup2 is active for example by checking for existence of `/sys/fs/cgroup/cgroup.controllers` file and based on that the values for `quota` and `period` could be taken from `cpu.max` file.


Discussion:
Can I take this issue?
Of course, @MariaMozgunova, thanks very much for offering to contribute!

Can you please verify whether I am going in the right direction?

In the [official documentation](https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html) it stated that `cpu.max` file has the following format: `$MAX $PERIOD`. 

In case both of the values are integers, `quota` and `period` can be equal to `$MAX` and `$PERIOD` respectively?

In addition, documentation states that `“max” for $MAX indicates no limit. If only one number is written, $MAX is updated.` In these two cases should the `multiprocessing.cpu_count()` value be returned?
Hi @MariaMozgunova 

Good question. The thing is that the current fallback to ``return multiprocessing.cpu_count()`` is supposed to be there for when docker is not used, because precisely ``return multiprocessing.cpu_count()`` will not return the right result for docker, this is why this feature checking these files was added in the first place.

The second case, if MAX is updated with an integer value, then it seems it is correct, only PERIOD is dropped, so that value could be used. For the first case, I really don't know. Maybe it is safer to return 1 than to overflow the CPUs? @maikelvdh any suggestion?

In any case, please make sure to modify the files in ``conan.tools``, not the legacy ones ``conans.xxx`` (these could be modified if strictly necessary and very low risk, otherwise, we prefer not to touch them at this moment that we are already in 2.0-beta). Thanks!
I have tried to run tests without making any changes to the source code. However, running tests both with `tox` and without it as specified on the README on the front page gives too many errors. Am I missing something?
![running tests conan too many errors](https://user-images.githubusercontent.com/63199273/179349927-7c6a4651-099d-40d9-b163-d90ae8ff9c90.png)
Hi @MariaMozgunova 

Yes, there are many tests that require to have some tools installed, like different build systems. You can check the ``conftest.py`` file, it contains a json declarative syntax to enable/disable/configure required tools. You can write your own ``confuser_test.py`` overriding and disabling some tools.

If you are going to touch such a very specific tool, I'd recommend looking in the ``unittest`` and ``integration`` test folders, the one that are covering the existing functionality and just run them (the folder or the file with ``pytest <path/to/folder>``. It is going to be much faster and convenient. Don't worry, our CI run all tests for PRs.

Resolving patch:
diff --git a/conan/tools/build/cpu.py b/conan/tools/build/cpu.py
--- a/conan/tools/build/cpu.py
+++ b/conan/tools/build/cpu.py
@@ -1,5 +1,6 @@
 import math
 import multiprocessing
+import os
 
 from conans.util.files import load
 
@@ -15,8 +16,18 @@ def _cpu_count():
     try:
         try:
             # This is necessary to deduce docker cpu_count
-            cfs_quota_us = int(load("/sys/fs/cgroup/cpu/cpu.cfs_quota_us"))
-            cfs_period_us = int(load("/sys/fs/cgroup/cpu/cpu.cfs_period_us"))
+            cfs_quota_us = cfs_period_us = 0
+            # cgroup2
+            if os.path.exists("/sys/fs/cgroup/cgroup.controllers"):
+                cpu_max = load("/sys/fs/cgroup/cpu.max").split()
+                if cpu_max[0] != "max":
+                    if len(cpu_max) == 1:
+                        cfs_quota_us, cfs_period_us = int(cpu_max[0]), 100_000
+                    else:
+                        cfs_quota_us, cfs_period_us = map(int, cpu_max)
+            else:  # cgroup1
+                cfs_quota_us = int(load("/sys/fs/cgroup/cpu/cpu.cfs_quota_us"))
+                cfs_period_us = int(load("/sys/fs/cgroup/cpu/cpu.cfs_period_us"))
             if cfs_quota_us > 0 and cfs_period_us > 0:
                 return int(math.ceil(cfs_quota_us / cfs_period_us))
         except (EnvironmentError, TypeError):


Test patch: